pandas: fix second 0002 collision — rename list-scan stub to pandas-0003

This commit is contained in:
russell@unturf.com 2026-03-29 22:08:05 -04:00
parent 0f7d8485f0
commit 2785a0ea1e
4 changed files with 228 additions and 115 deletions

View file

@ -10,6 +10,18 @@ import java.util.*;
* slow(): counts ops using list membership (words.contains(w)) O(W) per insert
* fast(): counts ops using a parallel HashSet for O(1) membership
* Assert: slowOps >= fastOps * 10 for W=500 deps per variable
*
* numpy-0002: recfunctions.stack_arrays seen=[] list dedup O(A×F²) field-name tracking
* Real code (numpy/lib/recfunctions.py:1396-1405):
* seen = []
* if name not in seen: seen.append(name) # O(F) per insert
* Fix: seen = set()
*
* numpy-0003: recfunctions.join_by names list rebuilt inside loop + .index() O(F²)
* Real code (numpy/lib/recfunctions.py:1610-1612):
* names = [name for name, dtype in ndtype] # O(F) rebuild per iteration
* nameidx = names.index(fname) # O(F) linear search
* Fix: name_to_idx dict for O(1) lookup
*/
public class NumpyTest {
@ -97,12 +109,142 @@ public class NumpyTest {
System.out.println("numpy-0001 PASS");
}
// numpy-0002: stack_arrays seen=[] list dedup
/** Simulates stack_arrays seen=[] field dedup — returns total list comparisons */
static long slowStackArrays(int numArrays, int fieldsPerArray) {
List<String> seen = new ArrayList<>();
long ops = 0;
for (int a = 0; a < numArrays; a++) {
for (int f = 0; f < fieldsPerArray; f++) {
String name = "field_" + f; // shared field names across arrays
boolean found = false;
for (int k = 0; k < seen.size(); k++) {
ops++;
if (seen.get(k).equals(name)) { found = true; break; }
}
if (!found) seen.add(name);
}
}
return ops;
}
/** Simulates fixed stack_arrays with seen=set() — returns total ops */
static long fastStackArrays(int numArrays, int fieldsPerArray) {
Set<String> seen = new HashSet<>();
long ops = 0;
for (int a = 0; a < numArrays; a++) {
for (int f = 0; f < fieldsPerArray; f++) {
String name = "field_" + f;
ops++; // O(1) set probe
seen.add(name);
}
}
return ops;
}
static void testStackArrays() {
int A = 100, F = 200; // 100 arrays, 200 fields each
long slowOps = slowStackArrays(A, F);
long fastOps = fastStackArrays(A, F);
System.out.printf(
"numpy-0002 A=%-4d F=%-4d slowOps=%-10d fastOps=%-8d ratio=%.1fx%n",
A, F, slowOps, fastOps, (double) slowOps / fastOps
);
assert slowOps > fastOps * 30 :
"numpy-0002 FAIL: expected >30x ratio; slow=" + slowOps + " fast=" + fastOps;
System.out.println("numpy-0002 PASS");
}
// numpy-0003: join_by names-list rebuild + .index()
/** Simulates join_by ndtype names-list rebuild + .index() — returns total comparisons */
static long slowJoinBy(int f1, int f2) {
List<String> ndtype = new ArrayList<>();
long ops = 0;
for (int i = 0; i < f1; i++) ndtype.add("r1_field_" + i);
for (int i = 0; i < f2; i++) {
String fname = "r2_field_" + i;
// Rebuild list (simulates list comprehension O(F) per iteration)
List<String> names = new ArrayList<>(ndtype);
ops += names.size();
// .index() linear search
int idx = -1;
for (int k = 0; k < names.size(); k++) {
ops++;
if (names.get(k).equals(fname)) { idx = k; break; }
}
if (idx == -1) ndtype.add(fname);
}
// Site 2: output dtype names tuple membership check
List<String> outputNames = new ArrayList<>(ndtype);
for (int i = 0; i < f1; i++) {
String f = "r1_field_" + i;
for (int k = 0; k < outputNames.size(); k++) {
ops++;
if (outputNames.get(k).equals(f)) break;
}
}
for (int i = 0; i < f2; i++) {
String f = "r2_field_" + i;
for (int k = 0; k < outputNames.size(); k++) {
ops++;
if (outputNames.get(k).equals(f)) break;
}
}
return ops;
}
/** Simulates fixed join_by with name_to_idx dict — returns total ops */
static long fastJoinBy(int f1, int f2) {
Map<String, Integer> nameToIdx = new HashMap<>();
List<String> ndtype = new ArrayList<>();
long ops = 0;
for (int i = 0; i < f1; i++) {
nameToIdx.put("r1_field_" + i, i);
ndtype.add("r1_field_" + i);
}
for (int i = 0; i < f2; i++) {
String fname = "r2_field_" + i;
ops++; // O(1) dict probe
if (!nameToIdx.containsKey(fname)) {
nameToIdx.put(fname, ndtype.size());
ndtype.add(fname);
}
}
// Site 2: set membership
Set<String> outputNamesSet = new HashSet<>(ndtype);
for (int i = 0; i < f1 + f2; i++) ops++;
return ops;
}
static void testJoinBy() {
int F1 = 200, F2 = 200;
long slowOps = slowJoinBy(F1, F2);
long fastOps = fastJoinBy(F1, F2);
System.out.printf(
"numpy-0003 F1=%-4d F2=%-4d slowOps=%-10d fastOps=%-8d ratio=%.1fx%n",
F1, F2, slowOps, fastOps, (double) slowOps / fastOps
);
assert slowOps > fastOps * 50 :
"numpy-0003 FAIL: expected >50x ratio; slow=" + slowOps + " fast=" + fastOps;
System.out.println("numpy-0003 PASS");
}
// main
public static void main(String[] args) {
int pass = 0, total = 1;
int pass = 0, total = 3;
try { testDependDict(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
try { testStackArrays(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
try { testJoinBy(); pass++; } catch (AssertionError e) { System.err.println(e.getMessage()); }
System.out.printf("%n%d/%d PASS%n", pass, total);
if (pass != total) System.exit(1);