java-topology/defects/vim/unit/InsComplDedupTest.java

186 lines
6.6 KiB
Java

package unit;
import java.util.*;
/**
* vim-0001: ins_compl_add linked-list linear scan O(N²) in insert-mode completion.
*
* Models ins_compl_add_matches() calling ins_compl_add() for N candidates.
* Each ins_compl_add() does a full O(M) walk of the existing completions linked
* list to check for duplicates. Total work is O(N²).
*
* The fix replaces the linked-list scan with a HashSet lookup: O(N) total.
*/
public class InsComplDedupTest {
// ----- SLOW path: linked-list dedup (mirrors Vim's insexpand.c) -----
static class ComplNode {
String str;
ComplNode next;
ComplNode(String s) { this.str = s; }
}
/** Returns op count for adding one candidate to the linked list. */
static long slowAddCandidate(ComplNode head, String candidate, long[] ops) {
// Walk linked list to check for duplicates — O(M)
ComplNode cur = head;
while (cur != null) {
ops[0]++;
if (cur.str.equals(candidate)) {
return -1; // already present
}
cur = cur.next;
}
return 0; // not found — would be added
}
static long slowBuildCompletions(String[] candidates) {
long[] ops = {0};
ComplNode head = null;
ComplNode tail = null;
List<String> accepted = new ArrayList<>();
for (String c : candidates) {
// Simulate the head-pointer (compl_first_match is not null)
if (head == null) {
head = new ComplNode(c);
tail = head;
accepted.add(c);
continue;
}
long result = slowAddCandidate(head, c, ops);
if (result == 0) {
// Add to list
ComplNode node = new ComplNode(c);
tail.next = node;
tail = node;
accepted.add(c);
}
}
return ops[0];
}
// ----- FAST path: HashSet dedup -----
static long fastBuildCompletions(String[] candidates) {
long ops = 0;
Set<String> seen = new HashSet<>();
for (String c : candidates) {
ops++; // O(1) hash lookup
seen.add(c);
}
return ops;
}
// ----- Test runner -----
public static void main(String[] args) {
System.out.println("vim-0001: InsComplDedupTest");
System.out.println("==========================");
int passed = 0;
int total = 0;
// Test 1: correctness — both paths accept same unique elements
{
total++;
String[] cands = {"alpha", "beta", "gamma", "alpha", "delta", "beta"};
// Slow path: collect accepted list
long[] ops = {0};
ComplNode head = null;
ComplNode tail = null;
List<String> slowAccepted = new ArrayList<>();
for (String c : cands) {
if (head == null) {
head = new ComplNode(c); tail = head;
slowAccepted.add(c); continue;
}
if (slowAddCandidate(head, c, ops) == 0) {
ComplNode node = new ComplNode(c); tail.next = node; tail = node;
slowAccepted.add(c);
}
}
Set<String> fastAccepted = new HashSet<>(Arrays.asList(cands));
boolean ok = new HashSet<>(slowAccepted).equals(fastAccepted);
System.out.println("Test 1 (correctness): " + (ok ? "PASS" : "FAIL"));
if (ok) passed++;
}
// Test 2: op-count ratio >= 5x at N=200 unique candidates
{
total++;
int N = 200;
String[] candidates = new String[N];
for (int i = 0; i < N; i++) candidates[i] = "word_" + i;
long slowOps = slowBuildCompletions(candidates);
long fastOps = fastBuildCompletions(candidates);
double ratio = (double) slowOps / fastOps;
System.out.printf("Test 2 (N=%d unique): slow=%d ops, fast=%d ops, ratio=%.1fx%n",
N, 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 N=500 unique candidates
{
total++;
int N = 500;
String[] candidates = new String[N];
for (int i = 0; i < N; i++) candidates[i] = "word_" + i;
long slowOps = slowBuildCompletions(candidates);
long fastOps = fastBuildCompletions(candidates);
double ratio = (double) slowOps / fastOps;
System.out.printf("Test 3 (N=%d unique): slow=%d ops, fast=%d ops, ratio=%.1fx%n",
N, 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 >= 100x at N=1000 unique candidates
{
total++;
int N = 1000;
String[] candidates = new String[N];
for (int i = 0; i < N; i++) candidates[i] = "word_" + i;
long slowOps = slowBuildCompletions(candidates);
long fastOps = fastBuildCompletions(candidates);
double ratio = (double) slowOps / fastOps;
System.out.printf("Test 4 (N=%d unique): slow=%d ops, fast=%d ops, ratio=%.1fx%n",
N, slowOps, fastOps, ratio);
boolean ok = ratio >= 100.0;
System.out.println("Test 4 (ratio >= 100x): " + (ok ? "PASS" : "FAIL"));
if (ok) passed++;
}
// Test 5: with duplicates — correctness and ratio hold
{
total++;
int N = 400;
String[] candidates = new String[N];
for (int i = 0; i < N; i++) candidates[i] = "dup_" + (i % 50); // 50 unique, 8 dups each
long slowOps = slowBuildCompletions(candidates);
long fastOps = fastBuildCompletions(candidates);
double ratio = (double) slowOps / fastOps;
System.out.printf("Test 5 (N=%d w/dups, 50 unique): slow=%d ops, fast=%d ops, ratio=%.1fx%n",
N, 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";
}
}