126 lines
4.4 KiB
Java
126 lines
4.4 KiB
Java
package unit;
|
||
|
||
import java.util.*;
|
||
|
||
/**
|
||
* Models KiCad ZONE_FILLER::Fill() iterative refill dedup.
|
||
*
|
||
* SLOW: O(Z_r × Z × L × Z×L) — std::find on growing vector per (zone, layer) pair.
|
||
* FAST: O(Z_r × Z × L) — HashSet dedup, O(1) insert/contains.
|
||
*
|
||
* CWE-407: pcbnew/zone_filler.cpp:998 (ZONE_FILLER::Fill iterative refill path)
|
||
*/
|
||
public class KicadZoneFillerAlgorithmTest {
|
||
|
||
record ZoneLayer(int zoneId, int layerId) {}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Slow — std::find O(N) scan on growing list
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class SlowZoneFiller {
|
||
long cmpOps = 0;
|
||
|
||
boolean find(List<ZoneLayer> list, ZoneLayer item) {
|
||
for (ZoneLayer zl : list) {
|
||
cmpOps++;
|
||
if (zl.equals(item)) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
List<ZoneLayer> getZonesToRefill(int zRemoved, int zTotal, int layers) {
|
||
List<ZoneLayer> zonesToRefill = new ArrayList<>();
|
||
for (int r = 0; r < zRemoved; r++) {
|
||
for (int z = 0; z < zTotal; z++) {
|
||
for (int l = 0; l < layers; l++) {
|
||
ZoneLayer item = new ZoneLayer(z, l);
|
||
if (!find(zonesToRefill, item)) {
|
||
zonesToRefill.add(item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return zonesToRefill;
|
||
}
|
||
|
||
long ops(int zRemoved, int zTotal, int layers) {
|
||
cmpOps = 0;
|
||
getZonesToRefill(zRemoved, zTotal, layers);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
// -------------------------------------------------------------------------
|
||
// Fast — HashSet O(1) dedup
|
||
// -------------------------------------------------------------------------
|
||
|
||
static class FastZoneFiller {
|
||
long cmpOps = 0;
|
||
|
||
List<ZoneLayer> getZonesToRefill(int zRemoved, int zTotal, int layers) {
|
||
Set<ZoneLayer> seen = new HashSet<>();
|
||
List<ZoneLayer> zonesToRefill = new ArrayList<>();
|
||
for (int r = 0; r < zRemoved; r++) {
|
||
for (int z = 0; z < zTotal; z++) {
|
||
for (int l = 0; l < layers; l++) {
|
||
ZoneLayer item = new ZoneLayer(z, l);
|
||
cmpOps++;
|
||
if (seen.add(item)) {
|
||
zonesToRefill.add(item);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return zonesToRefill;
|
||
}
|
||
|
||
long ops(int zRemoved, int zTotal, int layers) {
|
||
cmpOps = 0;
|
||
getZonesToRefill(zRemoved, zTotal, layers);
|
||
return cmpOps;
|
||
}
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
SlowZoneFiller slow = new SlowZoneFiller();
|
||
FastZoneFiller fast = new FastZoneFiller();
|
||
|
||
int passed = 0, total = 0;
|
||
|
||
System.out.println("=== kicad-0002: zone_filler std::find O(Z²×L²) ===");
|
||
|
||
// (zRemoved, zTotal, layers)
|
||
int[][] configs = {
|
||
{5, 20, 2}, // small board
|
||
{10, 50, 4}, // realistic (agent's estimate)
|
||
{15, 100, 6}, // large board
|
||
};
|
||
for (int[] cfg : configs) {
|
||
int zr = cfg[0], z = cfg[1], l = cfg[2];
|
||
long s = slow.ops(zr, z, l);
|
||
long f = fast.ops(zr, z, l);
|
||
double ratio = (double) s / Math.max(f, 1);
|
||
total++;
|
||
boolean ok = s > f && ratio >= 5.0;
|
||
System.out.printf("Zr=%2d Z=%3d L=%d slow=%9d fast=%6d ratio=%6.1fx %s%n",
|
||
zr, z, l, s, f, ratio, ok ? "PASS" : "FAIL");
|
||
if (ok) passed++;
|
||
}
|
||
|
||
// Correctness: both produce same set of zones to refill
|
||
SlowZoneFiller s2 = new SlowZoneFiller();
|
||
FastZoneFiller f2 = new FastZoneFiller();
|
||
List<ZoneLayer> sr = s2.getZonesToRefill(10, 50, 4);
|
||
List<ZoneLayer> fr = f2.getZonesToRefill(10, 50, 4);
|
||
Set<ZoneLayer> ss = new HashSet<>(sr), fs = new HashSet<>(fr);
|
||
total++;
|
||
boolean correct = ss.equals(fs);
|
||
System.out.printf("correctness (%d zones to refill): %s%n", sr.size(),
|
||
correct ? "PASS" : "FAIL");
|
||
if (correct) passed++;
|
||
|
||
System.out.printf("%n%d/%d PASS%n", passed, total);
|
||
if (passed < total) System.exit(1);
|
||
}
|
||
}
|