From 0405d11753a8a1bf9e163e43767c4e274867d773 Mon Sep 17 00:00:00 2001 From: mike Date: Wed, 14 Jan 2026 04:22:46 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 8 +-- src/main/java/puzzle/SwedishGenerator.java | 50 +++++++++++++------ src/test/java/puzzle/MainTest.java | 10 ++-- .../java/puzzle/SwedishGeneratorTest.java | 28 +++++------ 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 5d2a502..9941098 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -115,7 +115,7 @@ public record Export() { var sb = new StringBuilder(); for (var r = 0; r < R; r++) { if (r > 0) sb.append('\n'); - for (var c = 0; c < C; c++) sb.append((char) grid.byteAt(Grid.offset(r, c))); + for (var c = 0; c < C; c++) sb.append((char) grid.letter32At(Grid.offset(r, c))); } return sb.toString(); } @@ -137,9 +137,9 @@ public record Export() { for (var c = 0; c < C; c++) { var offset = Grid.offset(r, c); if (grid.isClue(offset)) { - sb.append(clueChar.replace(new Cell(grid, offset, grid.byteAt(offset)))); + sb.append(clueChar.replace(new Cell(grid, offset, grid.letter32At(offset)))); } else { - sb.append(NOT_CLUE_NOT_LETTER_TO(grid.byteAt(offset), emptyFallback)); + sb.append(NOT_CLUE_NOT_LETTER_TO(grid.letter32At(offset), emptyFallback)); } } out[r] = sb.toString(); @@ -227,7 +227,7 @@ public record Export() { for (var p : placed) { for (var c : p.cells) { if (inBounds(c) && g.notClue(c)) { - letterAt.put(c, (char) g.byteAt(c)); + letterAt.put(c, (char) g.letter32At(c)); } } } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index ae8d2f1..c80f57b 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -70,7 +70,14 @@ public record SwedishGenerator(Rng rng, int[] stack) { //72 << 3; static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } - record Pick(Slot slot, int[] indices, int count) { } + @AllArgsConstructor + static class Pick { + + Slot slot; + int[] indices; + int count; + } + // 0b11 //0b00 // 0b01 @@ -282,18 +289,14 @@ public record SwedishGenerator(Rng rng, int[] stack) { public Grid(byte[] g) { this(g, 0, 0); } static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); } - int digitAt(int index) { return g[index]; } public static int r(int offset) { return offset & 7; } public static int c(int offset) { return offset >>> 3; } static int offset(int r, int c) { return r | (c << 3); } - public byte byteAt(int pos) { return g[pos]; } + public byte letter32At(int pos) { return g[pos]; } void setLetter(int idx, byte ch) { g[idx] = ch; } void clearletter(int idx) { g[idx] = DASH; } - boolean isClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } boolean isClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) != X : ((hi >>> (index & 63)) & 1L) != X; } - boolean notClue(long index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } boolean notClue(int index) { return ((index & 64) == 0) ? ((lo >>> index) & 1L) == X : ((hi >>> (index & 63)) & 1L) == X; } - int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } void undoPlace(long maskLo, long maskHi) { for (long b = maskLo; b != 0; b &= b - 1) clearletter(Long.numberOfTrailingZeros(b)); @@ -785,7 +788,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { if (s.increasing()) { for (long b = s.lo; b != 0; b &= b - 1, i++) { int idx = Long.numberOfTrailingZeros(b); - byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); + byte cur = grid.letter32At(idx), ch = Lemma.byteAt(w, i); if (cur == DASH) { maskLo |= (1L << idx); grid.setLetter(idx, ch); @@ -796,7 +799,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { } for (long b = s.hi; b != 0; b &= b - 1, i++) { int idx = 64 | Long.numberOfTrailingZeros(b); - byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); + byte cur = grid.letter32At(idx), ch = Lemma.byteAt(w, i); if (cur == DASH) { maskHi |= (1L << (idx & 63)); grid.setLetter(idx, ch); @@ -809,7 +812,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (long b = s.hi; b != 0; i++) { int msb = 63 - Long.numberOfLeadingZeros(b); int idx = 64 | msb; - byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); + byte cur = grid.letter32At(idx), ch = Lemma.byteAt(w, i); if (cur == DASH) { maskHi |= (1L << msb); grid.setLetter(idx, ch); @@ -822,7 +825,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { for (long b = s.lo; b != 0; i++) { int msb = 63 - Long.numberOfLeadingZeros(b); int idx = msb; - byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i); + byte cur = grid.letter32At(idx), ch = Lemma.byteAt(w, i); if (cur == DASH) { maskLo |= (1L << msb); grid.setLetter(idx, ch); @@ -928,10 +931,12 @@ public record SwedishGenerator(Rng rng, int[] stack) { class Solver { + private final Pick CARRIER = new Pick(null, null, 0); long nodes; long backtracks; int lastMRV; long lastLog = t0; + Pick current = CARRIER; void renderProgress() { var now = System.currentTimeMillis(); if ((now - lastLog) < LOG_EVERY_MS) return; @@ -953,7 +958,7 @@ public record SwedishGenerator(Rng rng, int[] stack) { System.out.print("\r" + Strings.padRight(msg, 120)); System.out.flush(); } - Pick chooseMRV() { + void chooseMRV() { Slot best = null; for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { var s = slots[i]; @@ -962,7 +967,10 @@ public record SwedishGenerator(Rng rng, int[] stack) { var index = dictIndex[s.length()]; count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index, s.length()); - if (count == 0) return PICK_NOT_DONE; + if (count == 0) { + current = PICK_NOT_DONE; + return; + } if (best == null || count < count2 || (count == count2 && slotScores[i] > bestScore)) { @@ -973,11 +981,20 @@ public record SwedishGenerator(Rng rng, int[] stack) { } } // Re-calculate for the best slot to get actual indices - if (best == null) return PICK_DONE; + if (best == null) { + current = PICK_DONE; + return; + } var pattern = patternForSlot(grid, best); var index = dictIndex[best.length()]; - if (pattern == X) return new Pick(best, null, index.length); - return new Pick(best, candidateInfoForPattern(bitset, pattern, index, best.length()), index.length); + current = CARRIER; + current.slot = best; + current.count = index.length; + if (pattern == X) { + current.indices = null; + return; + } + current.indices = candidateInfoForPattern(bitset, pattern, index, best.length()); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; @@ -985,7 +1002,8 @@ public record SwedishGenerator(Rng rng, int[] stack) { if (20_000 > 0 && (System.currentTimeMillis() - t0) > 20_000) return false; - var pick = chooseMRV(); + chooseMRV(); + var pick = current; if (pick == PICK_DONE) return true; if (pick.slot == null) { backtracks++; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index a1acc16..771c43c 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -90,10 +90,10 @@ public class MainTest { grid.setLetter(OFF_0_0, LETTER_A); grid.setLetter(OFF_2_3, LETTER_Z); - Assertions.assertEquals(LETTER_A, grid.byteAt(OFF_0_0)); - Assertions.assertEquals(CLUE_UP, grid.byteAt(OFF_1_2)); - Assertions.assertEquals(LETTER_Z, grid.byteAt(OFF_2_3)); - Assertions.assertEquals(DASH, grid.byteAt(OFF_1_1)); + Assertions.assertEquals(LETTER_A, grid.letter32At(OFF_0_0)); + Assertions.assertEquals(CLUE_UP, grid.letter32At(OFF_1_2)); + Assertions.assertEquals(LETTER_Z, grid.letter32At(OFF_2_3)); + Assertions.assertEquals(DASH, grid.letter32At(OFF_1_1)); // Test isLetterAt Assertions.assertTrue(grid.notClue(OFF_0_0)); @@ -104,7 +104,7 @@ public class MainTest { // Test isDigitAt Assertions.assertFalse(grid.isClue(0)); Assertions.assertTrue(grid.isClue(OFF_1_2)); - Assertions.assertEquals(CLUE_UP, grid.digitAt(OFF_1_2)); + Assertions.assertEquals(CLUE_UP, clues.digitAt(OFF_1_2)); Assertions.assertFalse(grid.isClue(OFF_2_3)); Assertions.assertFalse(grid.isClue(OFF_1_1)); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index c64ac36..63bf94f 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -91,7 +91,7 @@ public class SwedishGeneratorTest { void testGrid() { var grid = Grid.createEmpty(); grid.setLetter(OFF_0_0, LETTER_A); - assertEquals('A', grid.byteAt(OFF_0_0)); + assertEquals('A', grid.letter32At(OFF_0_0)); } @Test @@ -311,9 +311,9 @@ public class SwedishGeneratorTest { // 1. Successful placement in empty grid assertTrue(placeWord(grid, s, w1, undoBuffer, 0)); - assertEquals('A', grid.byteAt(OFF_0_0)); - assertEquals('B', grid.byteAt(OFF_0_1)); - assertEquals('C', grid.byteAt(OFF_0_2)); + assertEquals('A', grid.letter32At(OFF_0_0)); + assertEquals('B', grid.letter32At(OFF_0_1)); + assertEquals('C', grid.letter32At(OFF_0_2)); assertEquals(lo, undoBuffer[0]); // 2. Successful placement with partial overlap (same characters) @@ -324,18 +324,18 @@ public class SwedishGeneratorTest { var w2 = Lemma.from("ABD"); assertFalse(placeWord(grid, s, w2, undoBuffer, 2)); // Verify grid is unchanged (still "ABC") - assertEquals('A', grid.byteAt(OFF_0_0)); - assertEquals('B', grid.byteAt(OFF_0_1)); - assertEquals('C', grid.byteAt(OFF_0_2)); + assertEquals('A', grid.letter32At(OFF_0_0)); + assertEquals('B', grid.letter32At(OFF_0_1)); + assertEquals('C', grid.letter32At(OFF_0_2)); // 4. Partial placement then conflict (rollback) grid = Grid.createEmpty(); grid.setLetter(OFF_0_2, LETTER_X); // Conflict at the end assertFalse(placeWord(grid, s, w1, undoBuffer, 3)); // Verify grid is still empty (except for 'X') - assertEquals(DASH, grid.byteAt(OFF_0_0)); - assertEquals(DASH, grid.byteAt(OFF_0_1)); - assertEquals('X', grid.byteAt(OFF_0_2)); + assertEquals(DASH, grid.letter32At(OFF_0_0)); + assertEquals(DASH, grid.letter32At(OFF_0_1)); + assertEquals('X', grid.letter32At(OFF_0_2)); } @Test @@ -350,13 +350,13 @@ public class SwedishGeneratorTest { var placed = placeWord(grid, s, w, undoBuffer, 0); assertTrue(placed); - assertEquals('A', grid.byteAt(OFF_0_1)); - assertEquals('Z', grid.byteAt(OFF_0_2)); + assertEquals('A', grid.letter32At(OFF_0_1)); + assertEquals('Z', grid.letter32At(OFF_0_2)); assertEquals(lo, undoBuffer[0]); grid.undoPlace(undoBuffer[0], undoBuffer[1]); - assertEquals(DASH, grid.byteAt(OFF_0_1)); - assertEquals(DASH, grid.byteAt(OFF_0_2)); + assertEquals(DASH, grid.letter32At(OFF_0_1)); + assertEquals(DASH, grid.letter32At(OFF_0_2)); } @Test