package unit; import java.util.ArrayList; import java.util.HashSet; import java.util.List; /** * Models vtkStaticCleanPolyData::RequestData() point-deduplication within cells. * * For each cell the filter collects unique mapped point IDs: * SLOW: std::find() on a growing std::vector — O(npts²) per cell * FAST: std::unordered_set insertion test — O(npts) per cell * * Total cost across C cells: O(C × npts²) vs O(C × npts). * * CWE-407: VTK Filters/Core/vtkStaticCleanPolyData.cxx:257,293,343,403 */ public class StaticCleanPolyDataAlgorithm { // ------------------------------------------------------------------------- // Slow (defective) implementation — mirrors the C++ std::find approach. // Returns the deduplicated point list and exposes total comparison ops. // ------------------------------------------------------------------------- static class SlowDedup { long totalOps = 0; /** Deduplicate ptsInCell using linear scan; returns ordered unique list. */ List dedup(int[] ptsInCell) { List cellIds = new ArrayList<>(); for (int ptId : ptsInCell) { boolean found = false; for (int existing : cellIds) { // O(k) scan — the defect totalOps++; if (existing == ptId) { found = true; break; } } if (!found) { cellIds.add(ptId); // account for the full scan that found nothing if (!found) { /* already counted above */ } } } return cellIds; } /** Process C cells each with the given point array. */ List processCell(int[] pts) { return dedup(pts); } } // ------------------------------------------------------------------------- // Fast (fixed) implementation — O(1) amortized via HashSet. // ------------------------------------------------------------------------- static class FastDedup { long totalOps = 0; List dedup(int[] ptsInCell) { HashSet seen = new HashSet<>(); List cellIds = new ArrayList<>(); for (int ptId : ptsInCell) { totalOps++; // one O(1) hash lookup per point if (seen.add(ptId)) { cellIds.add(ptId); } } return cellIds; } List processCell(int[] pts) { return dedup(pts); } } // ------------------------------------------------------------------------- // Helper: build a cell with npts points, last dupFrac fraction are dupes // ------------------------------------------------------------------------- static int[] buildCell(int npts, int uniquePts) { // pts[0..uniquePts-1] are unique IDs; rest repeat from start int[] pts = new int[npts]; for (int i = 0; i < npts; i++) { pts[i] = i % uniquePts; } return pts; } // ------------------------------------------------------------------------- // Tests // ------------------------------------------------------------------------- static int passed = 0; static int total = 0; static void check(String label, boolean condition) { total++; if (condition) { passed++; System.out.println(" PASS " + label); } else { System.out.println(" FAIL " + label); } } public static void main(String[] args) { System.out.println("=== StaticCleanPolyDataAlgorithm ==="); // --- Correctness: no duplicates --- { int[] pts = {10, 20, 30, 40}; SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); List slowResult = slow.dedup(pts); List fastResult = fast.dedup(pts); check("no-dup: slow size == 4", slowResult.size() == 4); check("no-dup: fast size == 4", fastResult.size() == 4); check("no-dup: results equal", slowResult.equals(fastResult)); } // --- Correctness: all duplicates --- { int[] pts = {7, 7, 7, 7, 7}; SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); List slowResult = slow.dedup(pts); List fastResult = fast.dedup(pts); check("all-dup: slow size == 1", slowResult.size() == 1); check("all-dup: fast size == 1", fastResult.size() == 1); check("all-dup: both return [7]", slowResult.equals(fastResult)); } // --- Correctness: mixed --- { int[] pts = {1, 2, 1, 3, 2, 4}; SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); List slowResult = slow.dedup(pts); List fastResult = fast.dedup(pts); check("mixed: slow size == 4", slowResult.size() == 4); check("mixed: fast size == 4", fastResult.size() == 4); check("mixed: results equal", slowResult.equals(fastResult)); } // --- Correctness: empty cell --- { int[] pts = {}; SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); check("empty: slow size == 0", slow.dedup(pts).size() == 0); check("empty: fast size == 0", fast.dedup(pts).size() == 0); } // --- Performance: O(npts²) vs O(npts) --- { // High-valence strip: npts = 128, all unique → slow must scan 0+1+2+...+127 = 8128 ops int npts = 128; int[] pts = buildCell(npts, npts); // all unique SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); int runs = 10_000; long t0 = System.nanoTime(); for (int r = 0; r < runs; r++) { slow.totalOps = 0; slow.dedup(pts); } long slowNs = System.nanoTime() - t0; long slowOpsPerCall = (npts * (npts - 1)) / 2; // expected: triangular number t0 = System.nanoTime(); for (int r = 0; r < runs; r++) { fast.totalOps = 0; fast.dedup(pts); } long fastNs = System.nanoTime() - t0; // Fast counts exactly npts hash ops per call fast.totalOps = 0; fast.dedup(pts); long fastOpsPerCall = fast.totalOps; double ratio = (double) slowNs / fastNs; System.out.printf(" INFO npts=%d slow_ops=%d fast_ops=%d ratio=%.1fx%n", npts, slowOpsPerCall, fastOpsPerCall, ratio); check("slow op count = npts*(npts-1)/2", slowOpsPerCall == (long) npts * (npts - 1) / 2); check("fast op count = npts", fastOpsPerCall == npts); check("fast is meaningfully faster (>= 1.5x)", ratio >= 1.5); } // --- Performance at larger scale: npts=256 --- { int npts = 256; int[] pts = buildCell(npts, npts); SlowDedup slow = new SlowDedup(); FastDedup fast = new FastDedup(); int runs = 5_000; long t0 = System.nanoTime(); for (int r = 0; r < runs; r++) slow.dedup(pts); long slowNs = System.nanoTime() - t0; t0 = System.nanoTime(); for (int r = 0; r < runs; r++) fast.dedup(pts); long fastNs = System.nanoTime() - t0; double ratio = (double) slowNs / fastNs; System.out.printf(" INFO npts=%d ratio=%.1fx%n", npts, ratio); check("npts=256 fast is meaningfully faster (>= 1.5x)", ratio >= 1.5); } System.out.println(); System.out.printf("%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }