introduce bitloops

This commit is contained in:
mike
2026-01-14 04:22:46 +01:00
parent eeae90a886
commit 0405d11753
4 changed files with 57 additions and 39 deletions

View File

@@ -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));
}
}
}

View File

@@ -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++;

View File

@@ -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));

View File

@@ -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