introduce bitloops

This commit is contained in:
mike
2026-01-12 06:42:22 +01:00
parent b7b66b5cd6
commit f655f70ab8
5 changed files with 38 additions and 36 deletions

View File

@@ -81,7 +81,7 @@ public record Export() {
public void clear() { Arrays.fill(bits, 0L); } public void clear() { Arrays.fill(bits, 0L); }
} }
record Placed(Lemma lemma, int startRow, int startCol, char direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) { record Placed(long lemma, int startRow, int startCol, char direction, int arrowRow, int arrowCol, int[] cells, boolean isReversed) {
public static final char HORIZONTAL = 'h'; public static final char HORIZONTAL = 'h';
static final char VERTICAL = 'v'; static final char VERTICAL = 'v';
@@ -91,8 +91,8 @@ public record Export() {
public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) { public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) {
public WordOut(Lemma l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) { public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) {
this(l.asWord(), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, l.simpel(), l.clue()); this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, Lemma.simpel(l), Lemma.clue(l));
} }
} }
@@ -101,7 +101,7 @@ public record Export() {
public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) { public record PuzzleResult(SwedishGenerator swe, Dict dict, Gridded mask, FillResult filled) {
boolean inBounds(int r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; } boolean inBounds(int r, int c) { return r >= 0 && r < SwedishGenerator.R && c >= 0 && c < SwedishGenerator.C; }
Placed extractPlacedFromSlot(Slot s, Lemma lemma) { Placed extractPlacedFromSlot(Slot s, long lemma) {
var d = s.dir(); var d = s.dir();
var cells = new int[s.len()]; var cells = new int[s.len()];

View File

@@ -108,12 +108,12 @@ public record SwedishGenerator(Rng rng) {
public static record FillResult(boolean ok, public static record FillResult(boolean ok,
Gridded grid, Gridded grid,
Map<Integer, Lemma> clueMap, Map<Integer, Long> clueMap,
FillStats stats) { FillStats stats) {
public void calcSimpel() { public void calcSimpel() {
if (ok) { if (ok) {
clueMap.forEach((k, v) -> stats.simplicity += v.simpel()); clueMap.forEach((k, v) -> stats.simplicity += Lemma.simpel(v));
stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size(); stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size();
} }
} }
@@ -243,7 +243,7 @@ public record SwedishGenerator(Rng rng) {
} }
} }
static record DictEntry(Lemma[] words, long[][] posBitsets) { } static record DictEntry(long[] words, long[][] posBitsets) { }
public static record Lemma(long word) { public static record Lemma(long word) {
@@ -261,13 +261,15 @@ public record SwedishGenerator(Rng rng) {
} }
public Lemma(int index, String word) { this(pack(index, word.getBytes(US_ASCII))); } public Lemma(int index, String word) { this(pack(index, word.getBytes(US_ASCII))); }
public Lemma(String word) { this(LEMMA_COUNTER++, word); } public Lemma(String word) { this(LEMMA_COUNTER++, word); }
byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | B64); }// word[]; } byte byteAt(int idx) { return Lemma.byteAt(word, idx); }// word[]; }
static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | B64); }// word[]; }
int intAt(int idx) { return (int) (((word >>> (idx * 5))) & 0b11111); }// word[]; } int intAt(int idx) { return (int) (((word >>> (idx * 5))) & 0b11111); }// word[]; }
@Override public int hashCode() { return unpackIndex(word); } @Override public int hashCode() { return unpackIndex(word); }
@Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.word == word); } @Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.word == word); }
String[] clue() { return CsvIndexService.clues(unpackIndex(word)); } static String[] clue(long w) { return CsvIndexService.clues(unpackIndex(w)); }
int simpel() { return CsvIndexService.simpel(unpackIndex(word)); } static int simpel(long w) { return CsvIndexService.simpel(unpackIndex(w)); }
int length() { int length() { return Lemma.length(word); }
static int length(long word) {
if (word == 0) return 0; if (word == 0) return 0;
int highestBit = 63 - Long.numberOfLeadingZeros(word & LETTER_MASK); int highestBit = 63 - Long.numberOfLeadingZeros(word & LETTER_MASK);
return (highestBit / 5) + 1; return (highestBit / 5) + 1;
@@ -277,9 +279,9 @@ public record SwedishGenerator(Rng rng) {
int highestBit = 63 - Long.numberOfLeadingZeros(p & LETTER_MASK); // 0-based int highestBit = 63 - Long.numberOfLeadingZeros(p & LETTER_MASK); // 0-based
return (highestBit / 5) + 1; return (highestBit / 5) + 1;
} }
public String asWord() { public static String asWord(long word) {
var b = new byte[length()]; var b = new byte[Lemma.length(word)];
for (var i = 0; i < length(); i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64); for (var i = 0; i < b.length; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64);
return new String(b, US_ASCII); return new String(b, US_ASCII);
} }
static int unpackIndex(long w) { static int unpackIndex(long w) {
@@ -312,7 +314,7 @@ public record SwedishGenerator(Rng rng) {
} }
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i); for (int i = MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i);
this(Arrays.stream(index).map(i -> { this(Arrays.stream(index).map(i -> {
var words = i.words().toArray(Lemma[]::new); var words = i.words().stream().mapToLong(ww -> ww.word).toArray();
int numWords = words.length; int numWords = words.length;
int numLongs = (numWords + 63) >>> 6; int numLongs = (numWords + 63) >>> 6;
var bitsets = new long[i.pos().length * 26][numLongs]; var bitsets = new long[i.pos().length * 26][numLongs];
@@ -677,13 +679,13 @@ public record SwedishGenerator(Rng rng) {
for (int i = 0; i < len; i++) cross += (count[s.pos(i)] - 1); for (int i = 0; i < len; i++) cross += (count[s.pos(i)] - 1);
return cross * 10 + len; return cross * 10 + len;
} }
static boolean placeWord(Grid grid, Slot s, Lemma w, int[] undoBuffer, int offset) { static boolean placeWord(Grid grid, Slot s, long w, int[] undoBuffer, int offset) {
int mask = 0; int mask = 0;
byte cur, ch; byte cur, ch;
for (int i = 0, leng = s.len(), idx; i < leng; 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 = Lemma.byteAt(w, i);
if (cur == DASH) { if (cur == DASH) {
mask |= (1 << i); mask |= (1 << i);
grid.setByteAt(idx, ch); grid.setByteAt(idx, ch);
@@ -778,9 +780,9 @@ public record SwedishGenerator(Rng rng) {
val used = new Bit1029(); val used = new Bit1029();
// val assigned = new HashMap<Integer, Lemma>(); // val assigned = new HashMap<Integer, Lemma>();
Lemma[] assigned = new Lemma[BIGG]; long[] assigned = new long[BIGG];
val ctx = CTX.get(); val ctx = CTX.get();
val count = ctx.cellCount; val count = ctx.cellCount;
Arrays.fill(count, 0, SIZE, 0); Arrays.fill(count, 0, SIZE, 0);
val slots = extractSlots(grid); val slots = extractSlots(grid);
@@ -803,7 +805,7 @@ public record SwedishGenerator(Rng rng) {
lastLog = (now); lastLog = (now);
var done = 0; var done = 0;
for (var lemma : assigned) { for (var lemma : assigned) {
if (lemma != null) done++; if (lemma != X) done++;
} }
var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100); var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100);
var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN)); var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN));
@@ -824,7 +826,7 @@ public record SwedishGenerator(Rng rng) {
int bestScore = -1; int bestScore = -1;
for (int i = 0, n = slots.size(); i < n; i++) { for (int i = 0, n = slots.size(); i < n; i++) {
var s = slots.get(i); var s = slots.get(i);
if (assigned[s.key()] != null) continue; if (assigned[s.key()] != X) continue;
var entry = dictIndex[s.len()]; var entry = dictIndex[s.len()];
if (entry == null) return PICK_NOT_DONE; if (entry == null) return PICK_NOT_DONE;
ctx.pattern = patternForSlot(grid, s); ctx.pattern = patternForSlot(grid, s);
@@ -880,7 +882,7 @@ public record SwedishGenerator(Rng rng) {
int idxInArray = (int) (r * r * r * L); int idxInArray = (int) (r * r * r * L);
var idx = idxs[idxInArray]; var idx = idxs[idxInArray];
var w = entry.words[idx]; var w = entry.words[idx];
var lemIdx = Lemma.unpackIndex(w.word); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
@@ -890,7 +892,7 @@ public record SwedishGenerator(Rng rng) {
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;
assigned[k] = null; assigned[k] = X;
used.clear(lemIdx); used.clear(lemIdx);
s.undoPlace(grid, ctx.undo[depth]); s.undoPlace(grid, ctx.undo[depth]);
} }
@@ -909,7 +911,7 @@ public record SwedishGenerator(Rng rng) {
double r = rng.nextFloat(); double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * N); int idxInArray = (int) (r * r * r * N);
var w = entry.words[idxInArray]; var w = entry.words[idxInArray];
var lemIdx = Lemma.unpackIndex(w.word); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (used.get(lemIdx)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
@@ -919,7 +921,7 @@ public record SwedishGenerator(Rng rng) {
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;
assigned[k] = null; assigned[k] = X;
used.clear(lemIdx); used.clear(lemIdx);
s.undoPlace(grid, ctx.undo[depth]); s.undoPlace(grid, ctx.undo[depth]);
} }
@@ -940,9 +942,9 @@ public record SwedishGenerator(Rng rng) {
} }
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
Map<Integer, Lemma> lemmaMap = new HashMap<>(); Map<Integer, Long> lemmaMap = new HashMap<>();
for (var i = 0; i < assigned.length; i++) { for (var i = 0; i < assigned.length; i++) {
if (assigned[i] != null) { if (assigned[i] != X) {
lemmaMap.put(i, assigned[i]); lemmaMap.put(i, assigned[i]);
} }
} }

View File

@@ -28,11 +28,11 @@ public class ExportFormatTest {
grid.setClue(0, (byte) '2'); grid.setClue(0, (byte) '2');
// This creates a slot starting at (0,1) // This creates a slot starting at (0,1)
var clueMap = new HashMap<Integer, Lemma>(); var clueMap = new HashMap<Integer, Long>();
// key = (cellIndex << 4) | direction // key = (cellIndex << 4) | direction
var key = (0 << 4) | 2; var key = (0 << 4) | 2;
var lemma = new Lemma("TEST"); var lemma = new Lemma("TEST");
clueMap.put(key, lemma); clueMap.put(key, lemma.word());
// Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4)
grid.setByteAt(Grid.offset(0, 1), (byte) 'T'); grid.setByteAt(Grid.offset(0, 1), (byte) 'T');

View File

@@ -167,7 +167,7 @@ public class MainTest {
// Regression baseline for seed search starting at 12347, pop 4, gens 20 // Regression baseline for seed search starting at 12347, pop 4, gens 20
Assertions.assertEquals(12348, foundSeed, "Found seed changed"); Assertions.assertEquals(12348, foundSeed, "Found seed changed");
Assertions.assertEquals(18, res.filled().clueMap().size(), "Number of assigned words changed"); Assertions.assertEquals(18, res.filled().clueMap().size(), "Number of assigned words changed");
Assertions.assertEquals("RIJTUIG", res.filled().clueMap().get(74).asWord()); Assertions.assertEquals("RIJTUIG", Lemma.asWord( res.filled().clueMap().get(74)));
Assertions.assertEquals(301794542151533187L, res.filled().grid().grid().lo); Assertions.assertEquals(301794542151533187L, res.filled().grid().grid().lo);
Assertions.assertEquals(193L, res.filled().grid().grid().hi); Assertions.assertEquals(193L, res.filled().grid().grid().hi);
} }

View File

@@ -108,7 +108,7 @@ public class SwedishGeneratorTest {
var l1 = new Lemma("APPLE"); var l1 = new Lemma("APPLE");
Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(l1.word())); Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(l1.word()));
assertEquals(5, l1.length()); assertEquals(5, Lemma.length(l1.word()));
assertEquals((byte) 'A', l1.byteAt(0)); assertEquals((byte) 'A', l1.byteAt(0));
assertEquals(1, l1.intAt(0)); assertEquals(1, l1.intAt(0));
@@ -120,7 +120,7 @@ public class SwedishGeneratorTest {
var entry3 = dict.index()[3]; var entry3 = dict.index()[3];
assertEquals(1, entry3.words().length); assertEquals(1, entry3.words().length);
assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(entry3.words()[0].word())); assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), Lemma.unpackLetters(entry3.words()[0]));
// Check pos indexing // Check pos indexing
// AXE: A at 0, X at 1, E at 2 // AXE: A at 0, X at 1, E at 2
@@ -276,7 +276,7 @@ public class SwedishGeneratorTest {
// r(i) and c(i) are used by placeWord. // r(i) and c(i) are used by placeWord.
var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14); var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14);
var s = Slot.from(0, packedPos, 3); var s = Slot.from(0, packedPos, 3);
var w1 = new Lemma("ABC"); var w1 = new Lemma("ABC").word();
var undoBuffer = new int[10]; var undoBuffer = new int[10];
// 1. Successful placement in empty grid // 1. Successful placement in empty grid
@@ -291,7 +291,7 @@ public class SwedishGeneratorTest {
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"); var w2 = new Lemma("ABD").word();
assertFalse(placeWord(grid, s, w2, undoBuffer, 2)); assertFalse(placeWord(grid, s, w2, undoBuffer, 2));
// Verify grid is unchanged (still "ABC") // Verify grid is unchanged (still "ABC")
assertEquals('A', grid.byteAt(Grid.offset(0, 0))); assertEquals('A', grid.byteAt(Grid.offset(0, 0)));
@@ -314,7 +314,7 @@ public class SwedishGeneratorTest {
// Slot at 0,1 length 2 // Slot at 0,1 length 2
var packedPos = ((long) Grid.offset(0, 1)) | (((long) Grid.offset(0, 2)) << 7); var packedPos = ((long) Grid.offset(0, 1)) | (((long) Grid.offset(0, 2)) << 7);
var s = Slot.from((0 << 8) | (1 << 4) | 2, packedPos, 2); var s = Slot.from((0 << 8) | (1 << 4) | 2, packedPos, 2);
var w = new Lemma("AZ"); var w = new Lemma("AZ").word();
var undoBuffer = new int[10]; var undoBuffer = new int[10];
var placed = placeWord(grid, s, w, undoBuffer, 0); var placed = placeWord(grid, s, w, undoBuffer, 0);