Gather data

This commit is contained in:
mike
2026-01-09 08:57:46 +01:00
parent fa806a1078
commit 171ea60636
3 changed files with 45 additions and 73 deletions

View File

@@ -172,7 +172,7 @@ public record ExportFormat() {
var d = s.dir(); var d = s.dir();
var cells = new int[s.len()]; var cells = new int[s.len()];
for (var i = 0; i < s.len(); i++) cells[i] = s.pos(i); for (int i = 0, len = s.len(); i < len; i++) cells[i] = s.pos(i);
// Canonicalize: always output right/down // Canonicalize: always output right/down
String direction; String direction;

View File

@@ -1,7 +1,7 @@
package puzzle; package puzzle;
import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.val;
import puzzle.ExportFormat.Bit; import puzzle.ExportFormat.Bit;
import puzzle.ExportFormat.Bit1029; import puzzle.ExportFormat.Bit1029;
import puzzle.ExportFormat.Gridded; import puzzle.ExportFormat.Gridded;
@@ -105,7 +105,7 @@ public record SwedishGenerator(int[] buff) {
Bit seen, Bit seen,
byte[] pattern, byte[] pattern,
IntList[] intListBuffer, IntList[] intListBuffer,
int[] undoBuffer) { int[] undo) {
public Context() { public Context() {
this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new Bit(), new byte[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH], this(new int[SIZE], new int[SIZE], new int[SIZE], new int[SIZE], new Bit(), new byte[MAX_WORD_LENGTH], new IntList[MAX_WORD_LENGTH],
@@ -388,7 +388,7 @@ public record SwedishGenerator(int[] buff) {
public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
} }
static void undoPlace(Grid grid, Slot s, int mask) { static void undoPlace(Grid grid, Slot s, int mask) {
for (var i = 0; i < s.len(); i++) { for (int i = 0, len = s.len(); i < len; i++) {
if ((mask & (1L << i)) != 0) { if ((mask & (1L << i)) != 0) {
grid.clear(s.pos(i)); grid.clear(s.pos(i));
} }
@@ -404,7 +404,7 @@ public record SwedishGenerator(int[] buff) {
grid.forEachSetBit71(idx -> { grid.forEachSetBit71(idx -> {
var d = grid.digitAt(idx); var d = grid.digitAt(idx);
var nbrs16 = OFFSETS[d]; var nbrs16 = OFFSETS[d];
int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c; int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) return; if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) return;
long packedPos = 0; long packedPos = 0;
var n = 0; var n = 0;
@@ -419,26 +419,6 @@ public record SwedishGenerator(int[] buff) {
visitor.visit((r << 8) | (c << 4) | d, packedPos, n); visitor.visit((r << 8) | (c << 4) | d, packedPos, n);
} }
}); });
/* for (var rci : IT) {
if (!grid.isDigitAt(rci.i)) continue;
var d = grid.digitAt(rci.i);
var nbrs16 = OFFSETS[d];
int rr = rci.r + nbrs16.r, cc = rci.c + nbrs16.c;
if (rr < 0 || rr >= R || cc < 0 || cc >= C || grid.isDigitAt(rr, cc)) continue;
long packedPos = 0;
var n = 0;
while (rr >= 0 && rr < R && cc >= 0 && cc < C && grid.isLettercell(rr, cc) && n < MAX_WORD_LENGTH) {
packedPos |= (long) Grid.offset(rr, cc) << (n * 7);
n++;
rr += nbrs16.dr;
cc += nbrs16.dc;
}
if (n > 0) {
visitor.visit((rci.r << 8) | (rci.c << 4) | d, packedPos, n);
}
}*/
} }
ArrayList<Slot> extractSlots(Grid grid) { ArrayList<Slot> extractSlots(Grid grid) {
@@ -470,7 +450,7 @@ public record SwedishGenerator(int[] buff) {
Arrays.fill(covH, 0, SIZE, 0); Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0); Arrays.fill(covV, 0, SIZE, 0);
boolean[] hasSlots = {false}; boolean[] hasSlots = { false };
grid.forEachSetBit71(clueIdx -> { grid.forEachSetBit71(clueIdx -> {
var d = grid.digitAt(clueIdx); var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d]; var nbrs16 = OFFSETS[d];
@@ -704,6 +684,9 @@ public record SwedishGenerator(int[] buff) {
record Pick(Slot slot, CandidateInfo info, boolean done) { } record Pick(Slot slot, CandidateInfo info, boolean done) { }
static final Pick PICK_DONE = new Pick(null, null, true);
static final Pick PICK_NOT_DONE = new Pick(null, null, false);
public static record FillResult(boolean ok, public static record FillResult(boolean ok,
Gridded grid, Gridded grid,
HashMap<Integer, Lemma> clueMap, HashMap<Integer, Lemma> clueMap,
@@ -721,21 +704,21 @@ public record SwedishGenerator(int[] buff) {
} }
static void patternForSlot(Grid grid, Slot s, byte[] pat) { static void patternForSlot(Grid grid, Slot s, byte[] pat) {
for (var i = 0; i < s.len(); i++) { for (int i = 0, len = s.len(); i < len; i++) {
var ch = grid.byteAt(s.pos(i)); var ch = grid.byteAt(s.pos(i));
pat[i] = isLetter(ch) ? ch : DASH; pat[i] = isLetter(ch) ? ch : DASH;
} }
} }
static int slotScore(int[] cellCount, Slot s) { static int slotScore(int[] count, Slot s) {
var cross = 0; int cross = 0, len = s.len();
for (var i = 0; i < s.len(); i++) cross += (cellCount[s.pos(i)] - 1); for (int i = 0; i < len; i++) cross += (count[s.pos(i)] - 1);
return cross * 10 + s.len(); return cross * 10 + len;
} }
static int placeWord(Grid grid, Slot s, Lemma w, int[] undoBuffer, int offset) { static boolean placeWord(Grid grid, Slot s, Lemma w, int[] undoBuffer, int offset) {
int mask = 0, idx; int mask = 0;
byte cur, ch; byte cur, ch;
for (var i = 0; i < s.len(); i++) { for (int i = 0, leng = s.len(), idx; i < leng; i++) {
idx = s.pos(i); idx = s.pos(i);
cur = grid.byteAt(idx); cur = grid.byteAt(idx);
ch = w.byteAt(i); ch = w.byteAt(i);
@@ -748,11 +731,11 @@ public record SwedishGenerator(int[] buff) {
grid.clear(s.pos(j)); grid.clear(s.pos(j));
} }
} }
return -1; return false;
} }
} }
undoBuffer[offset] = mask; undoBuffer[offset] = mask;
return 1; return true;
} }
public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex, public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex,
@@ -764,10 +747,10 @@ public record SwedishGenerator(int[] buff) {
var used = new Bit1029(); var used = new Bit1029();
var assigned = new HashMap<Integer, Lemma>(); var assigned = new HashMap<Integer, Lemma>();
var ctx = CTX.get(); var ctx = CTX.get();
var cellCount = ctx.cellCount; var count = ctx.cellCount;
Arrays.fill(cellCount, 0, SIZE, 0); Arrays.fill(count, 0, SIZE, 0);
for (var s : slots) for (var i = 0; i < s.len(); i++) cellCount[s.pos(i)]++; for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++;
var t0 = System.currentTimeMillis(); var t0 = System.currentTimeMillis();
final var lastLog = new AtomicLong(t0); final var lastLog = new AtomicLong(t0);
@@ -799,34 +782,29 @@ public record SwedishGenerator(int[] buff) {
Supplier<Pick> chooseMRV = () -> { Supplier<Pick> chooseMRV = () -> {
Slot best = null; Slot best = null;
CandidateInfo bestInfo = null; CandidateInfo bestInfo = null;
int bestSlot = -1;
for (var s : slots) { for (var s : slots) {
var k = s.key(); if (assigned.containsKey(s.key())) continue;
if (assigned.containsKey(k)) continue;
var entry = dictIndex[s.len()]; var entry = dictIndex[s.len()];
if (entry == null) { if (entry == null) return PICK_NOT_DONE;
return new Pick(null, null, false);
}
var patLen = s.len();
patternForSlot(grid, s, ctx.pattern); patternForSlot(grid, s, ctx.pattern);
var info = candidateInfoForPattern(ctx, entry, patLen); var info = candidateInfoForPattern(ctx, entry, s.len());
if (info.count == 0) {
return new Pick(null, null, false);
}
if (info.count == 0) return PICK_NOT_DONE;
var slotScore = -1;
if (best == null if (best == null
|| info.count < bestInfo.count || info.count < bestInfo.count
|| (info.count == bestInfo.count && slotScore(cellCount, s) > slotScore(cellCount, best))) { || (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) {
best = s; best = s;
bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s);
bestInfo = info; bestInfo = info;
if (info.count <= 1) break; if (info.count <= 1) break;
} }
} }
if (best == null) { if (best == null) {
return new Pick(null, null, true); return PICK_DONE;
} else { } else {
return new Pick(best, bestInfo, false); return new Pick(best, bestInfo, false);
} }
@@ -846,8 +824,8 @@ public record SwedishGenerator(int[] buff) {
stats.backtracks++; stats.backtracks++;
return false; return false;
} }
val info = pick.info;
stats.lastMRV = pick.info.count; stats.lastMRV = info.count;
renderProgress.run(); renderProgress.run();
var s = pick.slot; var s = pick.slot;
@@ -856,8 +834,8 @@ public record SwedishGenerator(int[] buff) {
var entry = dictIndex[patLen]; var entry = dictIndex[patLen];
var pat = new byte[patLen]; var pat = new byte[patLen];
patternForSlot(grid, s, pat); patternForSlot(grid, s, pat);
if (pick.info.indices != null && pick.info.indices.length > 0) { if (info.indices != null && info.indices.length > 0) {
var idxs = pick.info.indices; var idxs = info.indices;
var L = idxs.length; var L = idxs.length;
var tries = Math.min(MAX_TRIES_PER_SLOT, L); var tries = Math.min(MAX_TRIES_PER_SLOT, L);
@@ -878,8 +856,7 @@ public record SwedishGenerator(int[] buff) {
} }
if (!match) continue; if (!match) continue;
int nPlaced = placeWord(grid, s, w, ctx.undoBuffer, depth); if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
if (nPlaced < 0) continue;
used.set(w.index()); used.set(w.index());
assigned.put(k, w); assigned.put(k, w);
@@ -888,7 +865,7 @@ public record SwedishGenerator(int[] buff) {
assigned.remove(k); assigned.remove(k);
used.clear(w.index); used.clear(w.index);
undoPlace(grid, s, ctx.undoBuffer[depth]); undoPlace(grid, s, ctx.undo[depth]);
} }
stats.backtracks++; stats.backtracks++;
return false; return false;
@@ -917,8 +894,7 @@ public record SwedishGenerator(int[] buff) {
} }
if (!match) continue; if (!match) continue;
int nPlaced = placeWord(grid, s, w, ctx.undoBuffer, depth); if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
if (nPlaced < 0) continue;
used.set(w.index()); used.set(w.index());
assigned.put(k, w); assigned.put(k, w);
@@ -927,7 +903,7 @@ public record SwedishGenerator(int[] buff) {
assigned.remove(k); assigned.remove(k);
used.clear(w.index); used.clear(w.index);
undoPlace(grid, s, ctx.undoBuffer[depth]); undoPlace(grid, s, ctx.undo[depth]);
} }
stats.backtracks++; stats.backtracks++;

View File

@@ -264,22 +264,19 @@ public class SwedishGeneratorTest {
var undoBuffer = new int[10]; var undoBuffer = new int[10];
// 1. Successful placement in empty grid // 1. Successful placement in empty grid
int placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0); assertTrue(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 0));
assertEquals(1, placed);
assertEquals('A', grid.byteAt(0, 0)); assertEquals('A', grid.byteAt(0, 0));
assertEquals('B', grid.byteAt(0, 1)); assertEquals('B', grid.byteAt(0, 1));
assertEquals('C', grid.byteAt(0, 2)); assertEquals('C', grid.byteAt(0, 2));
assertEquals(0b111L, undoBuffer[0]); assertEquals(0b111L, undoBuffer[0]);
// 2. Successful placement with partial overlap (same characters) // 2. Successful placement with partial overlap (same characters)
placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 1); assertTrue(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 1));
assertEquals(1, placed);
assertEquals(0L, undoBuffer[1]); // 0 new characters placed assertEquals(0L, undoBuffer[1]); // 0 new characters placed
// 3. Conflict: place "ABD" where "ABC" is // 3. Conflict: place "ABD" where "ABC" is
var w2 = new Lemma("ABD", 1, "conflict"); var w2 = new Lemma("ABD", 1, "conflict");
placed = SwedishGenerator.placeWord(grid, s, w2, undoBuffer, 2); assertFalse(SwedishGenerator.placeWord(grid, s, w2, undoBuffer, 2));
assertEquals(-1, placed);
// Verify grid is unchanged (still "ABC") // Verify grid is unchanged (still "ABC")
assertEquals('A', grid.byteAt(0, 0)); assertEquals('A', grid.byteAt(0, 0));
assertEquals('B', grid.byteAt(0, 1)); assertEquals('B', grid.byteAt(0, 1));
@@ -288,8 +285,7 @@ public class SwedishGeneratorTest {
// 4. Partial placement then conflict (rollback) // 4. Partial placement then conflict (rollback)
grid = SwedishGenerator.makeEmptyGrid(); grid = SwedishGenerator.makeEmptyGrid();
grid.setCharAt(0, 2, 'X'); // Conflict at the end grid.setCharAt(0, 2, 'X'); // Conflict at the end
placed = SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 3); assertFalse(SwedishGenerator.placeWord(grid, s, w1, undoBuffer, 3));
assertEquals(-1, placed);
// Verify grid is still empty (except for 'X') // Verify grid is still empty (except for 'X')
assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 0)); assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 0));
assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1)); assertEquals(SwedishGenerator.DASH, grid.byteAt(0, 1));
@@ -306,7 +302,7 @@ public class SwedishGeneratorTest {
var undoBuffer = new int[10]; var undoBuffer = new int[10];
var placed = SwedishGenerator.placeWord(grid, s, w, undoBuffer, 0); var placed = SwedishGenerator.placeWord(grid, s, w, undoBuffer, 0);
assertEquals(1, placed); assertTrue(placed);
assertEquals('A', grid.byteAt(0, 1)); assertEquals('A', grid.byteAt(0, 1));
assertEquals('Z', grid.byteAt(0, 2)); assertEquals('Z', grid.byteAt(0, 2));
assertEquals(0b11L, undoBuffer[0]); assertEquals(0b11L, undoBuffer[0]);