introduce bitloops

This commit is contained in:
mike
2026-01-15 01:18:44 +01:00
parent e8711b30a1
commit ecbc408cce
7 changed files with 102 additions and 131 deletions

View File

@@ -100,7 +100,7 @@ public final class CsvIndexService
throw new RuntimeException("Index mismatch after rebuild. Requested=" + lineIndex + ", got line=" + preview(line)); throw new RuntimeException("Index mismatch after rebuild. Requested=" + lineIndex + ", got line=" + preview(line));
} }
private void ensureLoaded() throws IOException { public void ensureLoaded() throws IOException {
if (offsets != null && csvChannel != null && csvChannel.isOpen()) return; if (offsets != null && csvChannel != null && csvChannel.isOpen()) return;
synchronized (lock) { synchronized (lock) {

View File

@@ -131,9 +131,6 @@ public record Export() {
}).limit(Long.bitCount(lo))); }).limit(Long.bitCount(lo)));
} }
} }
char NOT_CLUE_NOT_LETTER_TO(byte b) {
return (char) (64 | b);
}
String gridToString(Clues clues) { String gridToString(Clues clues) {
var sb = new StringBuilder(INIT); var sb = new StringBuilder(INIT);
clues.forEachSlot((s, l, a) -> { clues.forEachSlot((s, l, a) -> {
@@ -174,34 +171,17 @@ public record Export() {
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64)); sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
}); });
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n"); return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
/*
var out = new String[R];
for (var r = 0; r < R; r++) {
var sb = new StringBuilder(C);
for (var c = 0; c < C; c++) {
var offset = Grid.offset(r, c);
if (clues.isClue(offset)) {
sb.append(clueChar.replace(new Cell(grid, clues, offset, clues.digitAt(offset))));
} else if (lisLetterAt(offset)) {
sb.append(NOT_CLUE_NOT_LETTER_TO(grid.letter32At(offset)));
} else {
sb.append(emptyFallback);
}
}
out[r] = sb.toString();
}
return out;*/
} }
} }
record Bit1029(long[] bits) { interface Bit1029 {
public Bit1029() { this(new long[2048]); } public static long[] bit1029() { return new long[2048]; }
static int wordIndex(int bitIndex) { return bitIndex >> 6; } static int wordIndex(int bitIndex) { return bitIndex >> 6; }
public boolean get(int bitIndex) { return (this.bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; } static public boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; }
public void set(int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; } static public void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; }
public void clear(int bitIndex) { this.bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); } static public void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); }
public void clear() { Arrays.fill(bits, 0L); } static public void clear(long[] bits) { Arrays.fill(bits, 0L); }
} }
record Placed(long lemma, int slotKey, int[] cells) { record Placed(long lemma, int slotKey, int[] cells) {

View File

@@ -3,6 +3,7 @@ package puzzle;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.val;
import puzzle.SwedishGenerator.Rng; import puzzle.SwedishGenerator.Rng;
import java.io.IOException; import java.io.IOException;
@@ -60,7 +61,14 @@ public class Main {
public void main(String[] args) { public void main(String[] args) {
var csv = Paths.get("nl_score_hints_v3.csv"); var csv = Paths.get("nl_score_hints_v3.csv");
var idx = Paths.get("nl_score_hints_v3.idx"); var idx = Paths.get("nl_score_hints_v3.idx");
ScopedValue.where(SC, new CsvIndexService(csv, idx)).run(() -> _main(args)); try {
val scv = new CsvIndexService(csv, idx);
scv.ensureLoaded();
ScopedValue.where(SC, scv).run(() -> _main(args));
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
public void _main(String[] args) { public void _main(String[] args) {
var opts = parseArgs(args); var opts = parseArgs(args);

View File

@@ -13,6 +13,7 @@ import puzzle.Export.DictEntryDTO;
import puzzle.Export.Gridded; import puzzle.Export.Gridded;
import puzzle.Export.Strings; import puzzle.Export.Strings;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@@ -20,7 +21,7 @@ import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Locale; import java.util.Locale;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import static java.nio.charset.StandardCharsets.*; import static java.nio.charset.StandardCharsets.US_ASCII;
/** /**
* NOTE: * NOTE:
@@ -66,6 +67,8 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
static final int STACK_SIZE = 64; static final int STACK_SIZE = 64;
static final char C_DASH = '\0'; static final char C_DASH = '\0';
static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH;
static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L;
static final long RANGE_0_624 = 624L - 0L + 1L;
//72 << 3; //72 << 3;
static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1; 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)); } static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
@@ -140,11 +143,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
} }
public int wordCount() { public int wordCount() {
int k = 0; int k = 0;
for (var n = 1; n < clueMap.length; n++) { for (var n = 1; n < clueMap.length; n++) if (clueMap[n] != X) k++;
if (clueMap[n] != X) {
k++;
}
}
return k; return k;
} }
} }
@@ -165,38 +164,26 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
x = y; x = y;
return y; return y;
} }
byte randbyte(int min, int max) {
var u = (nextU32() & 0xFFFFFFFFL);
var range = (long) max - (long) min + 1L;
return (byte) (min + (u % range));
}
int randint2bit() { return nextU32() & 3; } int randint2bit() { return nextU32() & 3; }
byte randint2bitByte() { return (byte) (nextU32() & 3); } byte randint2bitByte() { return (byte) (nextU32() & 3); }
int randint(int min, int max) { int randint(int max) { return (int) (((nextU32() & 0xFFFFFFFFL) % ((long) max - 0L + 1L))); }
var u = (nextU32() & 0xFFFFFFFFL); int randint0_SIZE() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_SIZE)); }
var range = (long) max - (long) min + 1L; int randint0_624() { return (int) (((nextU32() & 0xFFFFFFFFL) % RANGE_0_624)); }
return (int) (min + (u % range));
}
double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; }
int biasedIndexPow3(int N) { int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); }
int m = Math.min(nextU32(), Math.min(nextU32(), nextU32()));
return (int) (((m & 0xFFFFFFFFL) * (long) N) >>> 32);
}
} }
@AllArgsConstructor @AllArgsConstructor
static class Clues { static class Clues {
long lo, hi, vlo, vhi, rlo, rhi; long lo, hi, vlo, vhi, rlo, rhi;
public static Clues createEmpty() { public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0); }
return new Clues(0, 0, 0, 0, 0, 0);
}
public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); } public Clues deepCopyGrid() { return new Clues(lo, hi, vlo, vhi, rlo, rhi); }
boolean clueless(int idx) { boolean clueless(int idx) {
if (!isClue(idx)) return false; if (!isClue(idx)) return false;
if ((idx & 64) == 0) { if ((idx & 64) == 0) {
clearClueLo(idx); clearClueLo(idx);
}else{ } else {
clearClueHi(idx); clearClueHi(idx);
} }
return true; return true;
@@ -260,7 +247,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
return (Long.bitCount(matchLo & MASK_LO) + Long.bitCount(matchHi & MASK_HI)) / SIZED; return (Long.bitCount(matchLo & MASK_LO) + Long.bitCount(matchHi & MASK_HI)) / SIZED;
} }
public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); } public Grid toGrid() { return new Grid(new byte[SIZE], lo, hi); }
public void forEachSlot(SlotVisitor visitor) { public void forEachSlot(SwedishGenerator.SlotVisitor visitor) {
for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 1)); for (var l = lo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 1));
for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 0)); for (var l = lo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 0));
for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 2)); for (var l = lo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(Long.numberOfTrailingZeros(l), 2));
@@ -308,10 +295,12 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
static String[] clue(long w) { return CsvIndexService.clues(unpackIndex(w)); } static String[] clue(long w) { return CsvIndexService.clues(unpackIndex(w)); }
static int simpel(long w) { return CsvIndexService.simpel(unpackIndex(w)); } static int simpel(long w) { return CsvIndexService.simpel(unpackIndex(w)); }
static int length(long word) { return ((63 - Long.numberOfLeadingZeros(word & LETTER_MASK)) / 5) + 1; } static int length(long word) { return ((63 - Long.numberOfLeadingZeros(word & LETTER_MASK)) / 5) + 1; }
static ThreadLocal<byte[]> BYTES = ThreadLocal.withInitial(() -> new byte[SwedishGenerator.MAX_WORD_LENGTH]);
public static String asWord(long word) { public static String asWord(long word) {
var b = new byte[Lemma.length(word)]; val len = Lemma.length(word);
for (var i = 0; i < b.length; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64); var b = BYTES.get();//new byte[Lemma.length(word)];
return new String(b, US_ASCII); for (int i = 0, bi = 0; i < len * 5; bi++, i += 5) b[bi] = (byte) (((word >>> i) & 31) | 64);
return new String(b, 0, 0, len);
} }
static int unpackIndex(long w) { return (int) (w >>> 40); } static int unpackIndex(long w) { return (int) (w >>> 40); }
static int unpackLetters(long w) { return (int) (w & LETTER_MASK); } static int unpackLetters(long w) { return (int) (w & LETTER_MASK); }
@@ -330,12 +319,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
var entry = index[L]; var entry = index[L];
var idx = entry.words().size(); var idx = entry.words().size();
entry.words().add(lemma); entry.words().add(lemma);
for (var i = 0; i < L; i++) entry.pos()[i][Lemma.byteAt(lemma, i) - 1].add(idx);
for (var i = 0; i < L; i++) {
var letter = Lemma.byteAt(lemma, i) - 1;
if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma);
entry.pos()[i][letter].add(idx);
}
} }
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 -> {
@@ -360,7 +344,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
static Dict loadDict(String wordsPath) { static Dict loadDict(String wordsPath) {
try { try {
var map = new LongArrayList(100_000); var map = new LongArrayList(100_000);
Files.lines(Path.of(wordsPath), UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add)); Files.lines(Path.of(wordsPath), StandardCharsets.UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add));
return new Dict(map.toArray()); return new Dict(map.toArray());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -601,7 +585,8 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
Clues randomMask(final int clueSize) { Clues randomMask(final int clueSize) {
var g = Clues.createEmpty(); var g = Clues.createEmpty();
for (int placed = 0, guard = 0, idx; placed < clueSize && guard < 4000; guard++) { for (int placed = 0, guard = 0, idx; placed < clueSize && guard < 4000; guard++) {
idx = rng.randint(0, SIZE_MIN_1);
idx = rng.randint0_SIZE();
if (g.isClue(idx)) continue; if (g.isClue(idx)) continue;
var d_idx = rng.randint2bitByte(); var d_idx = rng.randint2bitByte();
if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d_idx)])) { if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(idx, d_idx)])) {
@@ -613,9 +598,9 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
} }
Clues mutate(Clues c) { Clues mutate(Clues c) {
int ri; int ri;
var bytes = MUTATE_RI[rng.randint(0, SIZE_MIN_1)]; var bytes = MUTATE_RI[rng.randint0_SIZE()];
for (var k = 0; k < 4; k++) { for (var k = 0; k < 4; k++) {
ri = bytes[rng.randint(0, 624)]; ri = bytes[rng.randint0_624()];
if (!c.clueless(ri)) { if (!c.clueless(ri)) {
var d_idx = rng.randint2bitByte(); var d_idx = rng.randint2bitByte();
if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClue(ri, d_idx); if (c.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotKey(ri, d_idx)])) c.setClue(ri, d_idx);
@@ -697,8 +682,8 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
for (var k = 0; k < offspring; k++) { for (var k = 0; k < offspring; k++) {
if (Thread.currentThread().isInterrupted()) break; if (Thread.currentThread().isInterrupted()) break;
var p1 = pop.get(rng.randint(0, pop.size() - 1)); var p1 = pop.get(rng.randint(pop.size() - 1));
var p2 = pop.get(rng.randint(0, pop.size() - 1)); var p2 = pop.get(rng.randint(pop.size() - 1));
var child = crossover(p1.grid, p2.grid); var child = crossover(p1.grid, p2.grid);
children.add(new GridAndFit(hillclimb(child, clueSize, 70))); children.add(new GridAndFit(hillclimb(child, clueSize, 70)));
} }
@@ -731,39 +716,34 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
return best.grid; return best.grid;
} }
static long patternForSlot(Grid grid, final int key, final long lo, final long hi) { static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) {
final long glo = grid.lo, ghi = grid.hi; if (((lo & glo) | (hi & ghi)) == X) {
if ((lo & glo) == X && (hi & ghi) == X) {
return 0; return 0;
} }
long p = 0; long p = 0;
if (Slot.increasing(key)) { if (Slot.increasing(key)) {
for (long b = lo & glo; b != X; b &= b - 1) { for (long b = lo & glo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
byte val = grid.g[idx];
int i = Long.bitCount(lo & ((1L << idx) - 1)); int i = Long.bitCount(lo & ((1L << idx) - 1));
p |= ((long) (i * 26 + val)) << (i << 3); p |= ((long) (i * 26 + g[idx])) << (i << 3);
} }
int offset = Long.bitCount(lo); int offset = Long.bitCount(lo);
for (long b = hi & ghi; b != X; b &= b - 1) { for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
byte val = grid.g[64 | idx];
int i = offset + Long.bitCount(hi & ((1L << idx) - 1)); int i = offset + Long.bitCount(hi & ((1L << idx) - 1));
p |= ((long) (i * 26 + val)) << (i << 3); p |= ((long) (i * 26 + g[64 | idx])) << (i << 3);
} }
} else { } else {
int offset = Long.bitCount(hi); int offset = Long.bitCount(hi);
for (long b = hi & ghi; b != X; b &= b - 1) { for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
byte val = grid.g[64 | idx];
int i = Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))); int i = Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)));
p |= ((long) (i * 26 + val)) << (i << 3); p |= ((long) (i * 26 + g[64 | idx])) << (i << 3);
} }
for (long b = lo & glo; b != X; b &= b - 1) { for (long b = lo & glo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
byte val = grid.g[idx];
int i = offset + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))); int i = offset + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)));
p |= ((long) (i * 26 + val)) << (i << 3); p |= ((long) (i * 26 + g[idx])) << (i << 3);
} }
} }
return p; return p;
@@ -774,28 +754,28 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
for (long b = hi; b != X; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1); for (long b = hi; b != X; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1);
return cross * 10 + Slot.length(lo, hi); return cross * 10 + Slot.length(lo, hi);
} }
static boolean placeWord(Grid grid, final int key, final long lo, final long hi, final long w) { static boolean placeWord(final Grid grid, final byte[] g, final int key, final long lo, final long hi, final long w) {
final long glo = grid.lo, ghi = grid.hi; final long glo = grid.lo, ghi = grid.hi;
if (Slot.increasing(key)) { if (Slot.increasing(key)) {
for (long b = lo & glo; b != X; b &= b - 1) { for (long b = lo & glo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
if (grid.g[idx] != Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1)))) return false; if (g[idx] != Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1)))) return false;
} }
int bcLo = Long.bitCount(lo); int bcLo = Long.bitCount(lo);
for (long b = hi & ghi; b != X; b &= b - 1) { for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
if (grid.g[64 | idx] != Lemma.byteAt(w, bcLo + Long.bitCount(hi & ((1L << idx) - 1)))) return false; if (g[64 | idx] != Lemma.byteAt(w, bcLo + Long.bitCount(hi & ((1L << idx) - 1)))) return false;
} }
long maskLo = lo & ~glo, maskHi = hi & ~ghi; long maskLo = lo & ~glo, maskHi = hi & ~ghi;
if ((maskLo | maskHi) != X) { if ((maskLo | maskHi) != X) {
for (long b = maskLo; b != X; b &= b - 1) { for (long b = maskLo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
grid.g[idx] = Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1))); g[idx] = Lemma.byteAt(w, Long.bitCount(lo & ((1L << idx) - 1)));
} }
for (long b = maskHi; b != X; b &= b - 1) { for (long b = maskHi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
grid.g[64 | idx] = Lemma.byteAt(w, bcLo + Long.bitCount(hi & ((1L << idx) - 1))); g[64 | idx] = Lemma.byteAt(w, bcLo + Long.bitCount(hi & ((1L << idx) - 1)));
} }
grid.lo |= maskLo; grid.lo |= maskLo;
grid.hi |= maskHi; grid.hi |= maskHi;
@@ -804,22 +784,22 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
int bcHi = Long.bitCount(hi); int bcHi = Long.bitCount(hi);
for (long b = hi & ghi; b != X; b &= b - 1) { for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
if (grid.g[64 | idx] != Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false; if (g[64 | idx] != Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))))) return false;
} }
for (long b = lo & glo; b != X; b &= b - 1) { for (long b = lo & glo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
if (grid.g[idx] != Lemma.byteAt(w, bcHi + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false; if (g[idx] != Lemma.byteAt(w, bcHi + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))))) return false;
} }
long maskLo = lo & ~glo, maskHi = hi & ~ghi; long maskLo = lo & ~glo, maskHi = hi & ~ghi;
if ((maskLo | maskHi) != X) { if ((maskLo | maskHi) != X) {
for (long b = maskHi; b != X; b &= b - 1) { for (long b = maskHi; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
grid.g[64 | idx] = Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)))); g[64 | idx] = Lemma.byteAt(w, Long.bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))));
} }
for (long b = maskLo; b != X; b &= b - 1) { for (long b = maskLo; b != X; b &= b - 1) {
int idx = Long.numberOfTrailingZeros(b); int idx = Long.numberOfTrailingZeros(b);
grid.g[idx] = Lemma.byteAt(w, bcHi + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))); g[idx] = Lemma.byteAt(w, bcHi + Long.bitCount(lo & ~((1L << idx) | ((1L << idx) - 1))));
} }
grid.lo |= maskLo; grid.lo |= maskLo;
grid.hi |= maskHi; grid.hi |= maskHi;
@@ -900,13 +880,14 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
} }
} }
public static FillResult fillMask(Rng rng, Slot[] slots, Grid mask) { public static SwedishGenerator.FillResult fillMask(Rng rng, Slot[] slots, Grid mask) {
val multiThreaded = Thread.currentThread().getName().contains("pool"); val multiThreaded = Thread.currentThread().getName().contains("pool");
val NO_LOG = (!Main.VERBOSE || multiThreaded); val NO_LOG = (!Main.VERBOSE || multiThreaded);
val grid = mask; val grid = mask;
val used = new Bit1029(); val used = new long[2048];
val assigned = new long[CLUE_INDEX_MAX_SIZE]; val assigned = new long[CLUE_INDEX_MAX_SIZE];
val bitset = new long[2500]; val bitset = new long[2500];
val g = grid.g;
val TOTAL = slots.length; val TOTAL = slots.length;
val slotScores = new int[TOTAL]; val slotScores = new int[TOTAL];
@@ -917,7 +898,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
class Solver { class Solver {
private final Pick CARRIER = new Pick(null, null, 0); private final SwedishGenerator.Pick CARRIER = new Pick(null, null, 0);
long nodes; long nodes;
long backtracks; long backtracks;
int lastMRV; int lastMRV;
@@ -949,7 +930,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) { for (int i = 0, count, count2 = -1, bestScore = -1, n = TOTAL; i < n; i++) {
var s = slots[i]; var s = slots[i];
if (assigned[s.key] != X) continue; if (assigned[s.key] != X) continue;
var pattern = patternForSlot(grid, s.key, s.lo, s.hi); var pattern = patternForSlot(grid.lo, grid.hi, g, s.key, s.lo, s.hi);
var index = s.entry; var index = s.entry;
count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong); count = pattern == X ? index.length : candidateCountForPattern(bitset, pattern, index.posBitsets, index.numlong);
@@ -971,7 +952,7 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
current = PICK_DONE; current = PICK_DONE;
return; return;
} }
var pattern = patternForSlot(grid, best.key, best.lo, best.hi); var pattern = patternForSlot(grid.lo, grid.hi, g, best.key, best.lo, best.hi);
var index = best.entry; var index = best.entry;
current = CARRIER; current = CARRIER;
current.slot = best; current.slot = best;
@@ -1017,18 +998,18 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
var idx = idxs[idxInArray]; var idx = idxs[idxInArray];
var w = entry.words[idx]; var w = entry.words[idx];
var lemIdx = Lemma.unpackIndex(w); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (Bit1029.get(used, lemIdx)) continue;
low = grid.lo; low = grid.lo;
top = grid.hi; top = grid.hi;
if (!placeWord(grid, k, slo, shi, w)) continue; if (!placeWord(grid, g, k, slo, shi, w)) continue;
used.set(lemIdx); Bit1029.set(used, lemIdx);
assigned[k] = w; assigned[k] = w;
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;
assigned[k] = X; assigned[k] = X;
used.clear(lemIdx); Bit1029.clear(used, lemIdx);
grid.lo = low; grid.lo = low;
grid.hi = top; grid.hi = top;
} }
@@ -1044,18 +1025,18 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
int idxInArray = (int) (r * r * r * (N - 1)); int idxInArray = (int) (r * r * r * (N - 1));
var w = entry.words[idxInArray]; var w = entry.words[idxInArray];
var lemIdx = Lemma.unpackIndex(w); var lemIdx = Lemma.unpackIndex(w);
if (used.get(lemIdx)) continue; if (Bit1029.get(used, lemIdx)) continue;
low = grid.lo; low = grid.lo;
top = grid.hi; top = grid.hi;
if (!placeWord(grid, k, slo, shi, w)) continue; if (!placeWord(grid, g, k, slo, shi, w)) continue;
used.set(lemIdx); Bit1029.set(used, lemIdx);
assigned[k] = w; assigned[k] = w;
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;
assigned[k] = X; assigned[k] = X;
used.clear(lemIdx); Bit1029.clear(used, lemIdx);
grid.lo = low; grid.lo = low;
grid.hi = top; grid.hi = top;
} }
@@ -1071,7 +1052,8 @@ public record SwedishGenerator(Rng rng, int[] stack, Clues cache) {
var ok = solver.backtrack(0); var ok = solver.backtrack(0);
// final progress line // final progress line
var res = new FillResult(ok, new Gridded(grid), assigned, new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV)); var res = new FillResult(ok, new Gridded(grid), assigned,
new SwedishGenerator.FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
if (!multiThreaded) { if (!multiThreaded) {
System.out.print("\r" + Strings.padRight("", 120) + "\r"); System.out.print("\r" + Strings.padRight("", 120) + "\r");
System.out.flush(); System.out.flush();

View File

@@ -52,7 +52,7 @@ public class ExportFormatTest {
var key = Slot.packSlotKey(0, CLUE_RIGHT); var key = Slot.packSlotKey(0, CLUE_RIGHT);
var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4); var lo = (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3) | (1L << OFF_0_4);
clueMap[key] = TEST; clueMap[key] = TEST;
assertTrue(placeWord(grid.grid(), key, lo, 0L, TEST)); assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST));
var fillResult = new FillResult(true, grid, clueMap, new FillStats(0, 0, 0, 0)); var fillResult = new FillResult(true, grid, clueMap, new FillStats(0, 0, 0, 0));
var puzzleResult = new PuzzleResult(new Clued(clues), fillResult); var puzzleResult = new PuzzleResult(new Clued(clues), fillResult);

View File

@@ -24,7 +24,6 @@ import static puzzle.SwedishGeneratorTest.OFF_0_2;
public class MainTest { public class MainTest {
static final Opts opts = new Main.Opts() {{ static final Opts opts = new Main.Opts() {{
this.seed = 12348; this.seed = 12348;
this.clueSize = 4; this.clueSize = 4;
@@ -44,8 +43,8 @@ public class MainTest {
val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); val key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
clues.setClue(OFF_0_0, CLUE_RIGHT); clues.setClue(OFF_0_0, CLUE_RIGHT);
var grid = new Gridded(clues.toGrid()); var grid = new Gridded(clues.toGrid());
val g = grid.grid().g;
placeWord(grid.grid(), key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB); placeWord(grid.grid(), g, key, (1L << OFF_0_1) | (1L << OFF_0_2), 0, AB);
var slots = extractSlots(clues, dict.index()); var slots = extractSlots(clues, dict.index());
assertEquals(1, slots.length); assertEquals(1, slots.length);
@@ -95,7 +94,7 @@ public class MainTest {
var grid = new Gridded(clues.toGrid()); var grid = new Gridded(clues.toGrid());
// Test set/get // Test set/get
placeWord(grid.grid(), key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ); placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_1_1) | (1L << OFF_0_1), 0, AZ);
val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); val arr = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1)); Assertions.assertEquals(LETTER_A, arr.get(OFF_1_1));
Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1)); Assertions.assertEquals(LETTER_Z, arr.get(OFF_0_1));
@@ -193,6 +192,8 @@ public class MainTest {
System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount()); System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount());
System.out.println("[DEBUG_LOG] Grid:"); System.out.println("[DEBUG_LOG] Grid:");
System.out.println(res.filled().grid().renderHuman(res.clues().mask())); System.out.println(res.filled().grid().renderHuman(res.clues().mask()));
System.out.println(res.filled().grid().gridToString(res.clues().mask()));
System.out.println(res.filled().grid().renderHuman(res.clues().mask()));
break; break;
} }
} }

View File

@@ -90,7 +90,7 @@ public class SwedishGeneratorTest {
var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT); var key = Slot.packSlotKey(OFF_0_0, CLUE_RIGHT);
val clues = Clues.createEmpty(); val clues = Clues.createEmpty();
clues.setClue(OFF_0_0, CLUE_RIGHT); clues.setClue(OFF_0_0, CLUE_RIGHT);
placeWord(grid.grid(), key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC); placeWord(grid.grid(), grid.grid().g, key, (1L << OFF_0_1) | (1L << OFF_0_2) | (1L << OFF_0_3), 0L, ABC);
val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); val map = grid.stream(clues).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(LETTER_A, map.get(OFF_0_1)); assertEquals(LETTER_A, map.get(OFF_0_1));
assertEquals(LETTER_B, map.get(OFF_0_2)); assertEquals(LETTER_B, map.get(OFF_0_2));
@@ -100,10 +100,10 @@ public class SwedishGeneratorTest {
@Test @Test
void testPatternForSlotMixed() { void testPatternForSlotMixed() {
var grid = createEmpty(); var grid = createEmpty();
placeWord(grid, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
placeWord(grid, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C")); placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_2_0, 0, Lemma.from(0, "C"));
var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT); var key = Slot.packSlotKey(OFF_1_0, CLUE_RIGHT);
var pattern = patternForSlot(grid, key, 7L, 0L); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
assertEquals(1L | (55L << 16), pattern); assertEquals(1L | (55L << 16), pattern);
} }
@@ -111,16 +111,16 @@ public class SwedishGeneratorTest {
void testPatternForSlotAllDashes() { void testPatternForSlotAllDashes() {
var grid = createEmpty(); var grid = createEmpty();
var key = Slot.packSlotKey(1 << Slot.BIT_FOR_DIR, CLUE_RIGHT); var key = Slot.packSlotKey(1 << Slot.BIT_FOR_DIR, CLUE_RIGHT);
var pattern = patternForSlot(grid, key, 7L, 0L); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
assertEquals(0L, pattern); assertEquals(0L, pattern);
} }
@Test @Test
void testPatternForSlotSingleLetter() { void testPatternForSlotSingleLetter() {
var grid = createEmpty(); var grid = createEmpty();
placeWord(grid, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); placeWord(grid, grid.g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
var key = Slot.packSlotKey(1, CLUE_RIGHT); var key = Slot.packSlotKey(1, CLUE_RIGHT);
var pattern = patternForSlot(grid, key, 7L, 0L); var pattern = patternForSlot(grid.lo, grid.hi, grid.g, key, 7L, 0L);
assertEquals(1L, pattern); assertEquals(1L, pattern);
} }
@Test @Test
@@ -134,8 +134,8 @@ public class SwedishGeneratorTest {
assertEquals(val1, rng2.nextU32()); assertEquals(val1, rng2.nextU32());
for (var i = 0; i < 100; i++) { for (var i = 0; i < 100; i++) {
var r = rng.randint(5, 10); var r = rng.randint(5);
assertTrue(r >= 5 && r <= 10); assertTrue(r >= 0 && r <= 5);
var f = rng.nextFloat(); var f = rng.nextFloat();
assertTrue(f >= 0.0 && f <= 1.0); assertTrue(f >= 0.0 && f <= 1.0);
} }
@@ -144,7 +144,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testGrid() { void testGrid() {
var grid = new Gridded(createEmpty()); var grid = new Gridded(createEmpty());
placeWord(grid.grid(), Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A")); placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_0, 0, Lemma.from(0, "A"));
val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); val arr = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(1, arr.size()); assertEquals(1, arr.size());
assertEquals(LETTER_A, arr.get(OFF_0_0)); assertEquals(LETTER_A, arr.get(OFF_0_0));
@@ -290,7 +290,7 @@ public class SwedishGeneratorTest {
var w1 = ABC; var w1 = ABC;
// 1. Successful placement in empty grid // 1. Successful placement in empty grid
assertTrue(placeWord(grid.grid(), key, lo, hi, w1)); assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(3, map.size()); assertEquals(3, map.size());
assertEquals(LETTER_A, map.get(OFF_0_0)); assertEquals(LETTER_A, map.get(OFF_0_0));
@@ -298,9 +298,9 @@ public class SwedishGeneratorTest {
assertEquals(LETTER_C, map.get(OFF_0_2)); assertEquals(LETTER_C, map.get(OFF_0_2));
// 2. Successful placement with partial overlap (same characters) // 2. Successful placement with partial overlap (same characters)
assertTrue(placeWord(grid.grid(), key, lo, hi, w1)); assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
// 3. Conflict: place "ABD" where "ABC" is // 3. Conflict: place "ABD" where "ABC" is
assertFalse(placeWord(grid.grid(), key, lo, hi, ABD)); assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, ABD));
// Verify grid is unchanged (still "ABC") // Verify grid is unchanged (still "ABC")
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(3, map.size()); assertEquals(3, map.size());
@@ -310,8 +310,8 @@ public class SwedishGeneratorTest {
// 4. Partial placement then conflict (rollback) // 4. Partial placement then conflict (rollback)
grid = new Gridded(createEmpty()); grid = new Gridded(createEmpty());
placeWord(grid.grid(), Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end placeWord(grid.grid(), grid.grid().g, Slot.packSlotKey(0, CLUE_RIGHT), 1L << OFF_0_2, 0, Lemma.from(0, "X")); // Conflict at the end
assertFalse(placeWord(grid.grid(), key, lo, hi, w1)); assertFalse(placeWord(grid.grid(), grid.grid().g, key, lo, hi, w1));
map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(1, map.size()); assertEquals(1, map.size());
assertEquals(LETTER_X, map.get(OFF_0_2)); assertEquals(LETTER_X, map.get(OFF_0_2));
@@ -326,7 +326,7 @@ public class SwedishGeneratorTest {
var w = AZ; var w = AZ;
val low = grid.grid().lo; val low = grid.grid().lo;
val top = grid.grid().hi; val top = grid.grid().hi;
var placed = placeWord(grid.grid(), key, lo, 0L, w); var placed = placeWord(grid.grid(), grid.grid().g, key, lo, 0L, w);
assertTrue(placed); assertTrue(placed);
var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter)); var map = grid.stream(Clues.createEmpty()).collect(Collectors.toMap(LetterAt::index, LetterAt::letter));
assertEquals(2, map.size()); assertEquals(2, map.size());