186 lines
6.6 KiB
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";
|
|
}
|
|
}
|