package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** * hudi-0002: InternalSchemaUtils.pruneInternalSchema ArrayList.contains O(N²) + pruneType O(F×D) * * Part A: topParentFieldIds dedup — ArrayList.contains in forEach = O(N²) * Part B: pruneType field membership — List.contains per field visit = O(F×D) * * Compile: javac -d . HudiPruneSchemaAlgorithm.java * Run: java -ea unit.HudiPruneSchemaAlgorithm */ public class HudiPruneSchemaAlgorithm { static long compareCount = 0; // --- Part A: topParentFieldIds dedup --- // Defective: ArrayList.contains dedup — O(N²) static List deduplicateParentIds_slow(List ids) { List result = new ArrayList<>(); for (int id : ids) { // ArrayList.contains = linear scan boolean found = false; for (int existing : result) { compareCount++; if (existing == id) { found = true; break; } } if (!found) result.add(id); } return result; } // Fixed: LinkedHashSet — O(N), preserves insertion order static List deduplicateParentIds_fast(List ids) { Set seen = new LinkedHashSet<>(); for (int id : ids) { compareCount++; // one hash op per entry seen.add(id); } return new ArrayList<>(seen); } // --- Part B: pruneType field membership --- // Defective: List fieldIds with contains() — O(F×D) static int pruneType_slow(int[] schema, List fieldIds) { int visited = 0; for (int fieldId : schema) { visited++; for (int fid : fieldIds) { compareCount++; if (fid == fieldId) break; } } return visited; } // Fixed: Set fieldIds with contains() — O(F) static int pruneType_fast(int[] schema, Set fieldIds) { int visited = 0; for (int fieldId : schema) { visited++; compareCount++; // O(1) hash lookup fieldIds.contains(fieldId); } return visited; } static void assertEq(String label, Object expected, Object actual) { if (!expected.equals(actual)) { throw new AssertionError(label + ": expected " + expected + " but got " + actual); } } public static void main(String[] args) { int pass = 0; int fail = 0; System.out.println("hudi-0002: InternalSchemaUtils.pruneInternalSchema"); // --- Part A tests --- // Test 1: correctness of dedup try { List ids = new ArrayList<>(); for (int i = 0; i < 5; i++) { ids.add(i % 3); } // [0,1,2,0,1] compareCount = 0; List slow = deduplicateParentIds_slow(ids); compareCount = 0; List fast = deduplicateParentIds_fast(ids); assertEq("dedup-size", slow.size(), fast.size()); assertEq("dedup-content", new HashSet<>(slow), new HashSet<>(fast)); System.out.println(" PASS dedup-correctness"); pass++; } catch (AssertionError e) { System.out.println(" FAIL dedup-correctness: " + e.getMessage()); fail++; } // Test 2: slow > fast at N=300 { List ids300 = new ArrayList<>(); for (int i = 0; i < 300; i++) ids300.add(i % 50); // 50 unique, many repeats compareCount = 0; deduplicateParentIds_slow(ids300); long slowOps = compareCount; compareCount = 0; deduplicateParentIds_fast(ids300); long fastOps = compareCount; System.out.printf(" dedup N=300(50unique): slow=%6d fast=%6d%n", slowOps, fastOps); if (slowOps > fastOps * 5) { System.out.println(" PASS slow > 5x fast for dedup"); pass++; } else { System.out.println(" FAIL expected slow > 5x fast for dedup"); fail++; } } // --- Part B tests --- // Test 3: correctness — both visit same fields try { int[] schema = new int[100]; for (int i = 0; i < 100; i++) schema[i] = i; List fieldIdList = new ArrayList<>(); Set fieldIdSet = new HashSet<>(); for (int i = 0; i < 20; i++) { fieldIdList.add(i * 5); fieldIdSet.add(i * 5); } compareCount = 0; int slowVisited = pruneType_slow(schema, fieldIdList); compareCount = 0; int fastVisited = pruneType_fast(schema, fieldIdSet); assertEq("pruneType-visited", slowVisited, fastVisited); System.out.println(" PASS pruneType-correctness"); pass++; } catch (AssertionError e) { System.out.println(" FAIL pruneType-correctness: " + e.getMessage()); fail++; } // Test 4: slow > fast at F=500, D=100 { int[] schema500 = new int[500]; for (int i = 0; i < 500; i++) schema500[i] = i; List fieldIdList = new ArrayList<>(); Set fieldIdSet = new HashSet<>(); for (int i = 0; i < 100; i++) { fieldIdList.add(i); fieldIdSet.add(i); } compareCount = 0; pruneType_slow(schema500, fieldIdList); long slowOps = compareCount; compareCount = 0; pruneType_fast(schema500, fieldIdSet); long fastOps = compareCount; System.out.printf(" pruneType F=500 D=100: slow=%6d fast=%6d%n", slowOps, fastOps); if (slowOps > fastOps * 10) { System.out.println(" PASS slow > 10x fast for pruneType"); pass++; } else { System.out.println(" FAIL expected slow > 10x fast for pruneType"); fail++; } } System.out.println(pass + "/" + (pass + fail) + " PASS"); if (fail > 0) throw new RuntimeException(fail + " tests failed"); } }