import java.util.*; /** * Unit test for libreoffice-0001: SavePivotTableXml member-to-cache O(M*C) linear scan. * * Defect: sc/source/filter/excel/xepivotxml.cxx SavePivotTableXml() * for (member : aMembers) * std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), member.maName) * * Fix: build std::unordered_map from aCacheFieldItems once, * then look up each member in O(1). * * Complexity: O(M*C) -> O(M+C) where M = member count, C = cache item count. */ public class LibreOfficePivotMemberLookupTest { // --- Defective implementation: std::find equivalent --- static int linearFind(List cacheItems, String name) { return cacheItems.indexOf(name); // O(C) } static List buildMemberSequenceLinear(List members, List cacheItems) { List result = new ArrayList<>(); Set used = new HashSet<>(); for (String member : members) { int pos = linearFind(cacheItems, member); // O(C) each -> O(M*C) total if (pos >= 0 && used.add(pos)) { result.add(pos); } } return result; } // --- Fixed implementation: unordered_map equivalent --- static List buildMemberSequenceFixed(List members, List cacheItems) { // Build O(1) lookup map once Map indexMap = new HashMap<>(cacheItems.size() * 2); for (int i = 0; i < cacheItems.size(); i++) { indexMap.put(cacheItems.get(i), i); } List result = new ArrayList<>(); Set used = new HashSet<>(); for (String member : members) { Integer pos = indexMap.get(member); // O(1) each if (pos != null && used.add(pos)) { result.add(pos); } } return result; } // --- Benchmark --- static long benchmarkLinear(int M, int C) { List cacheItems = new ArrayList<>(C); for (int i = 0; i < C; i++) cacheItems.add("item_" + i); List members = new ArrayList<>(M); for (int i = 0; i < M; i++) members.add("item_" + (i % C)); long start = System.nanoTime(); buildMemberSequenceLinear(members, cacheItems); return System.nanoTime() - start; } static long benchmarkFixed(int M, int C) { List cacheItems = new ArrayList<>(C); for (int i = 0; i < C; i++) cacheItems.add("item_" + i); List members = new ArrayList<>(M); for (int i = 0; i < M; i++) members.add("item_" + (i % C)); long start = System.nanoTime(); buildMemberSequenceFixed(members, cacheItems); return System.nanoTime() - start; } public static void main(String[] args) { System.out.println("=== libreoffice-0001: SavePivotTableXml pivot member lookup ==="); // Correctness test: both must produce same result { List cache = Arrays.asList("alpha", "beta", "gamma", "delta"); List members = Arrays.asList("gamma", "alpha", "delta", "alpha"); List linearResult = buildMemberSequenceLinear(members, cache); List fixedResult = buildMemberSequenceFixed(members, cache); if (!linearResult.equals(fixedResult)) { System.err.println("FAIL: correctness mismatch: linear=" + linearResult + " fixed=" + fixedResult); System.exit(1); } // gamma=2, alpha=0, delta=3 (alpha duplicate skipped) List expected = Arrays.asList(2, 0, 3); if (!linearResult.equals(expected)) { System.err.println("FAIL: wrong result: got=" + linearResult + " expected=" + expected); System.exit(1); } System.out.println("PASS: correctness verified, result=" + fixedResult); } // Missing member test { List cache = Arrays.asList("x", "y", "z"); List members = Arrays.asList("y", "missing", "x"); List linearResult = buildMemberSequenceLinear(members, cache); List fixedResult = buildMemberSequenceFixed(members, cache); if (!linearResult.equals(fixedResult)) { System.err.println("FAIL: missing-member mismatch"); System.exit(1); } System.out.println("PASS: missing-member handled correctly: " + fixedResult); } // Performance test: M=2000, C=2000 int M = 2000, C = 2000; // Warmup for (int w = 0; w < 3; w++) { benchmarkLinear(M/10, C/10); benchmarkFixed(M/10, C/10); } long linearNs = benchmarkLinear(M, C); long fixedNs = benchmarkFixed(M, C); double ratio = (double) linearNs / fixedNs; System.out.printf("Linear M=%d C=%d: %,d ns%n", M, C, linearNs); System.out.printf("Fixed M=%d C=%d: %,d ns%n", M, C, fixedNs); System.out.printf("Speedup: %.1fx%n", ratio); if (ratio < 3.0) { System.err.printf("FAIL: expected >= 3x speedup, got %.1fx%n", ratio); System.exit(1); } System.out.println("PASS: speedup >= 3x"); System.out.println("PASS: all tests passed"); } }