package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * tcl-0001: DoImport O(C×P) export pattern scan per namespace import * * Models Tcl's namespace import machinery. * SLOW: for each of C commands, linear scan P export patterns (current Tcl code) * FAST: pre-build a HashSet of exported command names; O(1) per command check * * In Tcl_Import with wildcard pattern, DoImport is called for each of C commands * that match the import pattern. Each DoImport call scans P export patterns * using Tcl_StringMatch. Total: O(C × P). */ public class TclDoImportAlgorithm { // Simple wildcard matcher (subset of Tcl_StringMatch) static boolean stringMatch(String str, String pattern) { if (pattern.equals("*")) return true; if (pattern.endsWith("*")) { String prefix = pattern.substring(0, pattern.length() - 1); return str.startsWith(prefix); } return str.equals(pattern); } // --- SLOW: linear scan of export patterns per command (current DoImport) --- static class SlowNamespace { Map cmdTable = new HashMap<>(); List exportPatterns = new ArrayList<>(); long opCount = 0; void defineCommand(String name) { cmdTable.put(name, new Object()); } void addExportPattern(String pattern) { exportPatterns.add(pattern); } /** Check if cmdName is exported: linear scan over P patterns */ boolean isExported(String cmdName) { for (String pattern : exportPatterns) { opCount++; if (stringMatch(cmdName, pattern)) { return true; } } return false; } /** * Simulate Tcl_Import with wildcard (imports all commands matching importPat * that are also exported). * Outer: O(C) hash iteration; inner: O(P) export check per command = O(C*P). */ int importAll(String importPat) { int count = 0; for (String cmdName : cmdTable.keySet()) { if (stringMatch(cmdName, importPat)) { if (isExported(cmdName)) { // O(P) per command count++; } } } return count; } } // --- FAST: HashSet cache of exported command names --- static class FastNamespace { Map cmdTable = new HashMap<>(); List exportPatterns = new ArrayList<>(); Set exportedCmdsCache = null; // null = needs rebuild long opCount = 0; void defineCommand(String name) { cmdTable.put(name, new Object()); exportedCmdsCache = null; // invalidate on new command } void addExportPattern(String pattern) { exportPatterns.add(pattern); exportedCmdsCache = null; // invalidate on export change } /** Build the exported-commands cache once: O(C * P) total, amortized O(1) per lookup */ void buildExportCache() { exportedCmdsCache = new HashSet<>(); for (String cmdName : cmdTable.keySet()) { for (String pattern : exportPatterns) { opCount++; if (stringMatch(cmdName, pattern)) { exportedCmdsCache.add(cmdName); break; } } } } /** Check if cmdName is exported: O(1) hash lookup */ boolean isExported(String cmdName) { if (exportedCmdsCache == null) { buildExportCache(); } opCount++; return exportedCmdsCache.contains(cmdName); } /** * Simulate Tcl_Import: O(C) outer, O(1) inner after cache build. * Cache is built once; subsequent imports are O(C). */ int importAll(String importPat) { int count = 0; for (String cmdName : cmdTable.keySet()) { if (stringMatch(cmdName, importPat)) { if (isExported(cmdName)) { // O(1) after cache count++; } } } return count; } } // --- Test harness --- static int passed = 0; static int total = 0; static void check(String name, boolean cond) { total++; if (cond) { passed++; System.out.println(" PASS: " + name); } else { System.out.println(" FAIL: " + name); } } public static void main(String[] args) { System.out.println("=== tcl-0001: DoImport O(C*P) export pattern scan ==="); int C = 1000; // commands in source namespace (e.g., Tk, Itcl) int P = 50; // export patterns SlowNamespace slow = new SlowNamespace(); FastNamespace fast = new FastNamespace(); // Define C commands: half with "pub_" prefix (to be exported), half with "priv_" for (int i = 0; i < C / 2; i++) { slow.defineCommand("pub_cmd_" + i); fast.defineCommand("pub_cmd_" + i); } for (int i = 0; i < C / 2; i++) { slow.defineCommand("priv_cmd_" + i); fast.defineCommand("priv_cmd_" + i); } // P export patterns: most export pub_cmd_* subsets // First pattern matches everything with prefix "pub_cmd_" slow.addExportPattern("pub_cmd_*"); fast.addExportPattern("pub_cmd_*"); // Add P-1 more specific patterns (won't match much, but still scanned) for (int i = 1; i < P; i++) { slow.addExportPattern("special_" + i + "_*"); fast.addExportPattern("special_" + i + "_*"); } // First import: correctness check int slowCount = slow.importAll("*"); int fastCount = fast.importAll("*"); check("slow imports correct count (" + (C/2) + ")", slowCount == C / 2); check("fast imports correct count (" + (C/2) + ")", fastCount == C / 2); check("slow and fast agree", slowCount == fastCount); // Correctness: specific commands check("slow: pub_cmd_0 is exported", slow.isExported("pub_cmd_0")); check("fast: pub_cmd_0 is exported", fast.isExported("pub_cmd_0")); check("slow: priv_cmd_0 is NOT exported", !slow.isExported("priv_cmd_0")); check("fast: priv_cmd_0 is NOT exported", !fast.isExported("priv_cmd_0")); // Op count comparison: measure 10 repeated imports (cache warm for fast) slow.opCount = 0; fast.opCount = 0; fast.exportedCmdsCache = null; // start fresh int importRuns = 10; for (int r = 0; r < importRuns; r++) { slow.importAll("*"); fast.importAll("*"); } long slowOps = slow.opCount; long fastOps = fast.opCount; double ratio = (double) slowOps / fastOps; System.out.println(); System.out.printf("C=%d commands, P=%d export patterns, %d import calls:%n", C, P, importRuns); System.out.printf(" SLOW ops (linear pattern scan per cmd): %,d%n", slowOps); System.out.printf(" FAST ops (cache build once + O(1) lookups): %,d%n", fastOps); System.out.printf(" Ratio: %.1fx%n", ratio); // SLOW: O(C*P) per call × 10 calls (early exit on first match for half, full scan for other half) check("slow ops >= C*importRuns", slowOps >= (long) C * importRuns); // FAST: O(C*P) once for cache build, then O(C) per subsequent import // Total fast ≈ C*P + (importRuns-1)*C check("fast ops <= (C*P + importRuns*C*2)", fastOps <= (long)(C * P + importRuns * C * 2)); check("ratio >= 5x", ratio >= 5.0); // Additional: multiple imports reuse cache (fast stays O(C), slow stays O(C*P)) slow.opCount = 0; fast.opCount = 0; int imports = 10; for (int i = 0; i < imports; i++) { slow.importAll("*"); fast.importAll("*"); } long slowOps2 = slow.opCount; long fastOps2 = fast.opCount; double ratio2 = (double) slowOps2 / fastOps2; System.out.printf("%nAfter %d repeated imports (cache hot for fast):%n", imports); System.out.printf(" SLOW ops: %,d%n", slowOps2); System.out.printf(" FAST ops: %,d%n", fastOps2); System.out.printf(" Ratio: %.1fx%n", ratio2); check("repeated import ratio >= 10x", ratio2 >= 10.0); System.out.println(); System.out.println(passed + "/" + total + " PASS"); if (passed != total) { System.exit(1); } } }