package unit; import java.util.*; /** * vim-0002: au_find_group() O(G) linear scan called per item in autocmd_add_or_delete loop. * * Models autocmd_add_or_delete() iterating over L list items and calling * au_find_group() (O(G) scan over augroups array) for each. * Total work: O(L×G). * * The fix uses a HashMap for O(1) group lookup. */ public class AuFindGroupTest { // ----- SLOW path: linear scan over augroups array (mirrors Vim's autocmd.c) ----- static int auFindGroupSlow(String[] augroups, String name, long[] ops) { for (int i = 0; i < augroups.length; i++) { ops[0]++; if (augroups[i] != null && augroups[i].equals(name)) { return i; } } return -1; // AUGROUP_ERROR } static long slowAutocmdAddOrDelete(String[] augroups, String[] listGroupNames) { long[] ops = {0}; for (String groupName : listGroupNames) { // au_find_group() call per list item int group = auFindGroupSlow(augroups, groupName, ops); if (group == -1) { // au_new_group also calls au_find_group first (already counted above) // then does another scan for a free slot for (int i = 0; i < augroups.length; i++) { ops[0]++; if (augroups[i] == null) break; } } } return ops[0]; } // ----- FAST path: HashMap lookup ----- static long fastAutocmdAddOrDelete(Map augroupsMap, String[] listGroupNames) { long ops = 0; for (String groupName : listGroupNames) { ops++; // O(1) hash lookup augroupsMap.get(groupName); // may return null (group not found) } return ops; } // ----- Helpers ----- static String[] buildAugroups(int G) { String[] augroups = new String[G]; for (int i = 0; i < G; i++) augroups[i] = "group_" + i; return augroups; } static Map buildAugroupsMap(int G) { Map map = new HashMap<>(); for (int i = 0; i < G; i++) map.put("group_" + i, i); return map; } static String[] buildListItems(int L, int G) { // Each list item references a random group from the pool String[] items = new String[L]; for (int i = 0; i < L; i++) items[i] = "group_" + (i % G); return items; } // ----- Test runner ----- public static void main(String[] args) { System.out.println("vim-0002: AuFindGroupTest"); System.out.println("========================"); int passed = 0; int total = 0; // Test 1: correctness — both paths resolve the same group indices { total++; int G = 10; String[] augroups = buildAugroups(G); Map augroupsMap = buildAugroupsMap(G); String[] queries = {"group_0", "group_5", "group_9", "group_99"}; boolean ok = true; long[] ops = {0}; for (String q : queries) { int slowResult = auFindGroupSlow(augroups, q, ops); Integer fastResult = augroupsMap.get(q); int fastInt = (fastResult == null) ? -1 : fastResult; if (slowResult != fastInt) { ok = false; break; } } System.out.println("Test 1 (correctness): " + (ok ? "PASS" : "FAIL")); if (ok) passed++; } // Test 2: op-count ratio >= 5x at L=100, G=20 { total++; int L = 100, G = 20; String[] augroups = buildAugroups(G); Map augroupsMap = buildAugroupsMap(G); String[] listItems = buildListItems(L, G); long slowOps = slowAutocmdAddOrDelete(augroups, listItems); long fastOps = fastAutocmdAddOrDelete(augroupsMap, listItems); double ratio = (double) slowOps / fastOps; System.out.printf("Test 2 (L=%d, G=%d): slow=%d ops, fast=%d ops, ratio=%.1fx%n", L, G, slowOps, fastOps, ratio); boolean ok = ratio >= 5.0; System.out.println("Test 2 (ratio >= 5x): " + (ok ? "PASS" : "FAIL")); if (ok) passed++; } // Test 3: op-count ratio >= 50x at L=500, G=100 { total++; int L = 500, G = 100; String[] augroups = buildAugroups(G); Map augroupsMap = buildAugroupsMap(G); String[] listItems = buildListItems(L, G); long slowOps = slowAutocmdAddOrDelete(augroups, listItems); long fastOps = fastAutocmdAddOrDelete(augroupsMap, listItems); double ratio = (double) slowOps / fastOps; System.out.printf("Test 3 (L=%d, G=%d): slow=%d ops, fast=%d ops, ratio=%.1fx%n", L, G, slowOps, fastOps, ratio); boolean ok = ratio >= 50.0; System.out.println("Test 3 (ratio >= 50x): " + (ok ? "PASS" : "FAIL")); if (ok) passed++; } // Test 4: op-count ratio >= 80x at L=500, G=100, all distinct group names { total++; int L = 500, G = 100; String[] augroups = buildAugroups(G); Map augroupsMap = buildAugroupsMap(G); // Each item references a distinct group — worst case for linear scan // when group is not found (searches entire array each time) String[] listItems = new String[L]; for (int i = 0; i < L; i++) listItems[i] = "missing_group_" + i; long slowOps = slowAutocmdAddOrDelete(augroups, listItems); long fastOps = fastAutocmdAddOrDelete(augroupsMap, listItems); double ratio = (double) slowOps / fastOps; System.out.printf("Test 4 (L=%d, G=%d, all-miss): slow=%d ops, fast=%d ops, ratio=%.1fx%n", L, G, slowOps, fastOps, ratio); boolean ok = ratio >= 80.0; System.out.println("Test 4 (ratio >= 80x): " + (ok ? "PASS" : "FAIL")); if (ok) passed++; } // Test 5: op-count ratio >= 5x at small scale L=50, G=10 { total++; int L = 50, G = 10; String[] augroups = buildAugroups(G); Map augroupsMap = buildAugroupsMap(G); String[] listItems = buildListItems(L, G); long slowOps = slowAutocmdAddOrDelete(augroups, listItems); long fastOps = fastAutocmdAddOrDelete(augroupsMap, listItems); double ratio = (double) slowOps / fastOps; System.out.printf("Test 5 (L=%d, G=%d): slow=%d ops, fast=%d ops, ratio=%.1fx%n", L, G, slowOps, fastOps, ratio); boolean ok = ratio >= 5.0; System.out.println("Test 5 (ratio >= 5x): " + (ok ? "PASS" : "FAIL")); if (ok) passed++; } System.out.println(); System.out.println(passed + "/" + total + " PASS"); assert passed == total : passed + "/" + total + " tests passed"; } }