introduce bitloops

This commit is contained in:
mike
2026-01-12 00:47:38 +01:00
parent 986c2f85a9
commit 84ba4c9c63
7 changed files with 164 additions and 143 deletions

View File

@@ -8,7 +8,6 @@ import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.*; import java.nio.file.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
public final class CsvIndexService public final class CsvIndexService
@@ -18,7 +17,7 @@ public final class CsvIndexService
static final Gson GSON = new Gson(); static final Gson GSON = new Gson();
private static final int MAGIC = 0x4C494458; // "LIDX" private static final int MAGIC = 0x4C494458; // "LIDX"
private static final int VERSION = 1; private static final int VERSION = 1;
static int SIMPEL_IDX = 3;
private final Path csvPath; private final Path csvPath;
private final Path idxPath; private final Path idxPath;
@@ -30,7 +29,10 @@ public final class CsvIndexService
this.csvPath = csvPath; this.csvPath = csvPath;
this.idxPath = idxPath; this.idxPath = idxPath;
} }
public static int lineToSimpel(String line) {
var parts = line.split(",", 5);
return Integer.parseInt(parts[SIMPEL_IDX].trim());
}
public static String[] lineToClue(String line) { public static String[] lineToClue(String line) {
if (line.isBlank()) throw new RuntimeException("Empty line"); if (line.isBlank()) throw new RuntimeException("Empty line");
var parts = line.split(",", 5); var parts = line.split(",", 5);
@@ -51,16 +53,23 @@ public final class CsvIndexService
throw new RuntimeException("Invalid word:" + line); throw new RuntimeException("Invalid word:" + line);
} }
// CSV has level 1-10. llmScores use 10-level.
int score = Integer.parseInt(parts[2].trim()); int score = Integer.parseInt(parts[2].trim());
if (score < 1) { if (score < 1) {
if (Main.VERBOSE) System.err.println("Word too complex: " + line); if (Main.VERBOSE) System.err.println("Word too complex: " + line);
return; return;
} }
int simpel = Integer.parseInt(parts[3].trim()); ok.accept(new Lemma(id, word));
ok.accept(new Lemma(id, word, simpel));
} }
public static int simpel(int index) {
try {
if (SC.isBound())
return lineToSimpel(SC.get().getLine(index));
return -1;
} catch (Exception e) {
throw new RuntimeException("Failed to get clues for index " + index, e);
}
}
public static String[] clues(int index) { public static String[] clues(int index) {
try { try {
if (SC.isBound()) if (SC.isBound())

View File

@@ -7,7 +7,6 @@ import puzzle.SwedishGenerator.Grid;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import static java.nio.charset.StandardCharsets.*;
import static puzzle.SwedishGenerator.R; import static puzzle.SwedishGenerator.R;
import static puzzle.SwedishGenerator.Lemma; import static puzzle.SwedishGenerator.Lemma;
import static puzzle.SwedishGenerator.Slot; import static puzzle.SwedishGenerator.Slot;
@@ -215,4 +214,25 @@ public record Export() {
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards); return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
} }
} }
static record DictEntryDTO(ArrayList<Lemma> words, IntListDTO[][] pos) {
public DictEntryDTO(int L) {
this(new ArrayList<>(), new IntListDTO[L][26]);
for (var i = 0; i < L; i++) for (var j = 0; j < 26; j++) pos[i][j] = new IntListDTO();
}
}
static final class IntListDTO {
int[] a = new int[8];
int n = 0;
void add(int v) {
if (n >= a.length) a = Arrays.copyOf(a, a.length * 2);
a[n++] = v;
}
int size() { return n; }
int[] data() { return a; }
}
} }

View File

@@ -74,8 +74,6 @@ public class Main {
section("Settings"); section("Settings");
printSettings(opts); printSettings(opts);
var csv = Paths.get("nl_score_hints_v3.csv");
var idx = Paths.get("nl_score_hints_v3.idx");
var res = generatePuzzle(opts); var res = generatePuzzle(opts);
if (res == null) { if (res == null) {
@@ -86,6 +84,7 @@ public class Main {
} }
section("Result"); section("Result");
res.filled().calcSimpel();
info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity)); info(String.format(Locale.ROOT, "simplicity : %.2f", res.filled().stats().simplicity));
section("Mask"); section("Mask");
@@ -353,6 +352,7 @@ public class Main {
TOTAL_NODES.addAndGet(filled.stats().nodes); TOTAL_NODES.addAndGet(filled.stats().nodes);
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks); TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
if (filled.ok()) { if (filled.ok()) {
filled.calcSimpel();
TOTAL_SUCCESS.incrementAndGet(); TOTAL_SUCCESS.incrementAndGet();
TOTAL_SIMPLICITY.addAndGet((long) (filled.stats().simplicity * 100)); TOTAL_SIMPLICITY.addAndGet((long) (filled.stats().simplicity * 100));
} }

View File

@@ -8,10 +8,10 @@ import precomp.Neighbors9x8.nbrs_8;
import precomp.Neighbors9x8.rci; import precomp.Neighbors9x8.rci;
import puzzle.Export.Bit; import puzzle.Export.Bit;
import puzzle.Export.Bit1029; import puzzle.Export.Bit1029;
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;
@@ -37,6 +37,8 @@ public record SwedishGenerator(Rng rng) {
public CandidateInfo(int n) { this(null, n); } public CandidateInfo(int n) { this(null, n); }
} }
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
//@formatter:off //@formatter:off
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } @FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
//@formatter:on //@formatter:on
@@ -67,6 +69,9 @@ public record SwedishGenerator(Rng rng) {
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)); }
record Pick(Slot slot, CandidateInfo info, boolean done) { } record Pick(Slot slot, CandidateInfo info, boolean done) { }
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
// Directions for '1'..'6' // Directions for '1'..'6'
static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS; static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS;
static final nbrs_16[] OFFSETS_FOUR = Neighbors9x8.OFFSETS_FOUR; static final nbrs_16[] OFFSETS_FOUR = Neighbors9x8.OFFSETS_FOUR;
@@ -75,6 +80,7 @@ public record SwedishGenerator(Rng rng) {
static final rci[] IT = Neighbors9x8.IT; static final rci[] IT = Neighbors9x8.IT;
static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED; static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED;
static final int[][] MUTATE_RI = new int[SIZE][625]; static final int[][] MUTATE_RI = new int[SIZE][625];
static final long[] NBR8_PACKED = Neighbors9x8.NBR8_PACKED;
static { static {
for (int i = 0; i < SIZE; i++) { for (int i = 0; i < SIZE; i++) {
@@ -87,28 +93,6 @@ public record SwedishGenerator(Rng rng) {
clamp(Grid.c(i) + dc1 + dc2, 0, C - 1)); clamp(Grid.c(i) + dc1 + dc2, 0, C - 1));
} }
} }
/*static {
Rng trng = new Rng(1);
for (int i = 0; i < SIZE; i++) {
int[] neighborhood = new int[625];
int k = 0;
for (int dr1 = -2; dr1 <= 2; dr1++)
for (int dr2 = -2; dr2 <= 2; dr2++)
for (int dc1 = -2; dc1 <= 2; dc1++)
for (int dc2 = -2; dc2 <= 2; dc2++)
neighborhood[k++] = Grid.offset(clamp(Grid.r(i) + dr1 + dr2, 0, R - 1),
clamp(Grid.c(i) + dc1 + dc2, 0, C - 1));
for (k = 0; k < 625; k++) {
long packed = 0;
for (int s = 0; s < 4; s++) {
int ri = neighborhood[trng.randint(0, 624)];
int d = trng.randint(1, 4);
packed |= ((long) ri | ((long) d << 8)) << (s << 4);
}
MUTATE_RI[i][k] = packed;
}
}
}*/
static final Pick PICK_DONE = new Pick(null, null, true); static final Pick PICK_DONE = new Pick(null, null, true);
static final Pick PICK_NOT_DONE = new Pick(null, null, false); static final Pick PICK_NOT_DONE = new Pick(null, null, false);
@@ -127,9 +111,9 @@ public record SwedishGenerator(Rng rng) {
HashMap<Integer, Lemma> clueMap, HashMap<Integer, Lemma> clueMap,
FillStats stats) { FillStats stats) {
public FillResult { public void calcSimpel() {
if (ok) { if (ok) {
clueMap.forEach((k, v) -> stats.simplicity += v.simpel); clueMap.forEach((k, v) -> stats.simplicity += v.simpel());
stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size(); stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size();
} }
} }
@@ -247,9 +231,7 @@ public record SwedishGenerator(Rng rng) {
} }
return true; return true;
} }
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
static boolean isLetter(byte b) { return (b & B64) != B0; } static boolean isLetter(byte b) { return (b & B64) != B0; }
public boolean isLetterSet(int idx) { return isLetter(g[idx]); } public boolean isLetterSet(int idx) { return isLetter(g[idx]); }
static boolean notDigit(byte b) { return (b & B48) != B48; } static boolean notDigit(byte b) { return (b & B48) != B48; }
@@ -268,27 +250,11 @@ public record SwedishGenerator(Rng rng) {
} }
} }
static final class IntList { static final record IntList(int[] data, int size) { }
int[] a = new int[8]; static record DictEntry(Lemma[] words, IntList[][] pos) { }
int n = 0;
void add(int v) {
if (n >= a.length) a = Arrays.copyOf(a, a.length * 2);
a[n++] = v;
}
int size() { return n; }
int[] data() { return a; }
}
static record DictEntry(ArrayList<Lemma> words, IntList[][] pos) { public static record Lemma(int index, long word, byte len) {
public DictEntry(int L) {
this(new ArrayList<>(), new IntList[L][26]);
for (var i = 0; i < L; i++) for (var j = 0; j < 26; j++) pos[i][j] = new IntList();
}
}
public static record Lemma(int index, long word, byte len, short simpel) {
static int LEMMA_COUNTER = 0; static int LEMMA_COUNTER = 0;
static long pack(byte[] b) { static long pack(byte[] b) {
@@ -296,15 +262,17 @@ public record SwedishGenerator(Rng rng) {
for (var i = 0; i < b.length; i++) w |= ((long) b[i] & ~64) << (i * 5); for (var i = 0; i < b.length; i++) w |= ((long) b[i] & ~64) << (i * 5);
return w; return w;
} }
public Lemma(int index, String word, int simpel) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length(), (short) simpel); } public Lemma(int index, String word) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length()); }
public Lemma(String word, int simpel) { this(LEMMA_COUNTER++, word, simpel); } public Lemma(String word) { this(LEMMA_COUNTER++, word); }
byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | Grid.B64); }// word[]; } byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | B64); }// word[]; }
int intAt(int idx) { return (int) (((word >>> (idx * 5))) & 0b11111); }// word[]; }
@Override public int hashCode() { return index; } @Override public int hashCode() { return index; }
@Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.index == index); } @Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.index == index); }
String[] clue() { return CsvIndexService.clues(index); } String[] clue() { return CsvIndexService.clues(index); }
int simpel() { return CsvIndexService.simpel(index); }
public String asWord() { public String asWord() {
var b = new byte[len]; var b = new byte[len];
for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | Grid.B64); for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64);
return new String(b, US_ASCII); return new String(b, US_ASCII);
} }
} }
@@ -314,23 +282,29 @@ public record SwedishGenerator(Rng rng) {
int length) { int length) {
public Dict(Lemma[] wordz) { public Dict(Lemma[] wordz) {
var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE]; var index = new DictEntryDTO[MAX_WORD_LENGTH_PLUS_ONE];
Arrays.setAll(index, i -> new DictEntry(i)); Arrays.setAll(index, i -> new DictEntryDTO(i));
for (var lemma : wordz) { for (var lemma : wordz) {
var L = lemma.len; var L = lemma.len;
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++) { for (var i = 0; i < L; i++) {
var letter = lemma.byteAt(i) - 'A'; var letter = lemma.intAt(i) - 1;
if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma); if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma);
entry.pos[i][letter].add(idx); 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(index, Arrays.stream(index).mapToInt(i -> i.words.size()).sum()); this(Arrays.stream(index).map(i -> new DictEntry(i.words().toArray(Lemma[]::new),
Arrays.stream(i.pos())
.map(ii -> Arrays.stream(ii).map(dto -> new IntList(dto.data(), dto.size()))
.toArray(IntList[]::new))
.toArray(IntList[][]::new)))
.toArray(DictEntry[]::new),
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
} }
static Dict loadDict(String wordsPath) { static Dict loadDict(String wordsPath) {
try { try {
@@ -394,12 +368,12 @@ public record SwedishGenerator(Rng rng) {
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
var d = grid.digitAt(idx); var d = grid.digitAt(idx);
var packed = OFFSETS[d].path()[idx]; var packed = OFFSETS[d].path()[idx];
long packedPos = 0; long packedPos = 0L;
int k = 0; int k = 0;
for (int n = (int) (packed >>> 56), iidx; k < n && k < MAX_WORD_LENGTH; k++) { for (long n = (packed >>> 56), offset = 0L, iidx; k < n; k++, offset += 7L) {
iidx = (int) ((packed >>> (k * 7)) & 0x7F); iidx = ((packed >>> offset) & 0x7FL);
if (grid.isClue(iidx)) break; if (grid.isClue(iidx)) break;
packedPos |= (long) iidx << (k * 7); packedPos |= iidx << offset;
} }
if (k > 0) { if (k > 0) {
visitor.visit((idx << 4) | d, packedPos, k); visitor.visit((idx << 4) | d, packedPos, k);
@@ -412,20 +386,36 @@ public record SwedishGenerator(Rng rng) {
return slots; return slots;
} }
long maskFitness(Grid grid) { static long fitting(Grid grid, int clueIdx, int[] covH, int[] covV) {
var ctx = CTX.get(); var d = grid.digitAt(clueIdx);
var covH = ctx.covH; var nbrs16 = OFFSETS[d];
var covV = ctx.covV; long packed = nbrs16.path()[clueIdx];
Arrays.fill(covH, 0, SIZE, 0); int n = (int) (packed >>> 56) * 7, k, idx;
Arrays.fill(covV, 0, SIZE, 0); var horiz = Slot.horiz(d) ? covH : covV;
for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) {
long lo_cl = grid.lo, hi_cl = grid.hi; idx = (int) ((packed >>> (k)) & 0x7F);
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); if (grid.isClue(idx)) break;
boolean hasSlots = false; horiz[idx] += 1;
}
for (int i = 0; i < 65; i += 64) { if (k > 0) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { if (k < MIN_LEN7) return 8001L;
int clueIdx = i + Long.numberOfTrailingZeros(bits); return 1L;
}
return 0L;
}
long maskFitness(Grid grid) {
var ctx = CTX.get();
var covH = ctx.covH;
var covV = ctx.covV;
Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0);
int clueIdx;
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = 0L;
for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
clueIdx = i + Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx); var d = grid.digitAt(clueIdx);
var nbrs16 = OFFSETS[d]; var nbrs16 = OFFSETS[d];
long packed = nbrs16.path()[clueIdx]; long packed = nbrs16.path()[clueIdx];
@@ -437,27 +427,27 @@ public record SwedishGenerator(Rng rng) {
horiz[idx] += 1; horiz[idx] += 1;
} }
if (k > 0) { if (k > 0) {
hasSlots = true;
if (k < MIN_LEN7) penalty += 8000; if (k < MIN_LEN7) penalty += 8000;
penalty |= 1;
} }
} }
} }
if (!hasSlots) return 1_000_000_000L; if ((penalty & 1) == 0) return 1_000_000_000L;
penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
var seen = ctx.seen; var seen = ctx.seen;
var stack = ctx.stack; var stack = ctx.stack;
seen.clear(); seen.clear();
for (int i = 0; i < 65; i += 64) { for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
int clueIdx = i + Long.numberOfTrailingZeros(bits); clueIdx = i + Long.numberOfTrailingZeros(bits);
if (seen.get(clueIdx)) continue; if (seen.get(clueIdx)) continue;
int size = 0; int size = 0;
stack[0] = clueIdx; stack[0] = clueIdx;
seen.set(clueIdx); seen.set(clueIdx);
for (int sp = 1; sp > 0; size++) { for (int sp = 1; sp > 0; size++) {
long packed = Neighbors9x8.NBR8_PACKED[stack[--sp]]; long packed = NBR8_PACKED[stack[--sp]];
for (int k = 0, n = (int) (packed >>> 56) * 7; k < n; k += 7) { for (int k = 0, n = (int) (packed >>> 56) * 7; k < n; k += 7) {
int nidx = (int) ((packed >>> k) & 0x7F); int nidx = (int) ((packed >>> k) & 0x7F);
if (seen.get(nidx) || grid.notClue(nidx)) continue; if (seen.get(nidx) || grid.notClue(nidx)) continue;
@@ -472,11 +462,11 @@ public record SwedishGenerator(Rng rng) {
for (int i = 0; i < 65; i += 64) { for (int i = 0; i < 65; i += 64) {
long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL)); long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL));
for (; bits != X; bits &= bits - 1) { for (; bits != X; bits &= bits - 1) {
int idx = i + Long.numberOfTrailingZeros(bits); clueIdx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[idx]; var rci = IT[clueIdx];
if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400;
var h = covH[idx]; var h = covH[clueIdx];
var v = covV[idx]; var v = covV[clueIdx];
if (h == 0 && v == 0) penalty += 1500; if (h == 0 && v == 0) penalty += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200;
else penalty += 600; else penalty += 600;
@@ -650,7 +640,7 @@ public record SwedishGenerator(Rng rng) {
undoBuffer[offset] = mask; undoBuffer[offset] = mask;
return true; return true;
} }
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) { static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) {
var pattern = ctx.pattern; var pattern = ctx.pattern;
var listBuffer = ctx.intListBuffer; var listBuffer = ctx.intListBuffer;
@@ -665,7 +655,7 @@ public record SwedishGenerator(Rng rng) {
} }
if (listCount == 0) { if (listCount == 0) {
return CANDIDATES[entry.words.size()]; return CANDIDATES[entry.words.length];
} }
// Sort constraints by size to optimize intersection // Sort constraints by size to optimize intersection
@@ -801,26 +791,26 @@ public record SwedishGenerator(Rng rng) {
double r = rng.nextFloat(); double r = rng.nextFloat();
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.get(idx); var w = entry.words[idx];
if (used.get(w.index())) continue; if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index()); used.set(w.index);
assigned.put(k, w); assigned.put(k, w);
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;
assigned.remove(k); assigned.remove(k);
used.clear(w.index()); used.clear(w.index);
s.undoPlace(grid, ctx.undo[depth]); s.undoPlace(grid, ctx.undo[depth]);
} }
stats.backtracks++; stats.backtracks++;
return false; return false;
} }
var N = entry.words.size(); var N = entry.words.length;
if (N == 0) { if (N == 0) {
stats.backtracks++; stats.backtracks++;
return false; return false;
@@ -830,13 +820,13 @@ public record SwedishGenerator(Rng rng) {
for (var t = 0; t < tries; t++) { for (var t = 0; t < tries; t++) {
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.get(idxInArray); var w = entry.words[idxInArray];
if (used.get(w.index())) continue; if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue; if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index()); used.set(w.index);
assigned.put(k, w); assigned.put(k, w);
if (backtrack(depth + 1)) return true; if (backtrack(depth + 1)) return true;

View File

@@ -31,7 +31,7 @@ public class ExportFormatTest {
var clueMap = new HashMap<Integer, Lemma>(); var clueMap = new HashMap<Integer, Lemma>();
// key = (cellIndex << 4) | direction // key = (cellIndex << 4) | direction
var key = (0 << 4) | 2; var key = (0 << 4) | 2;
var lemma = new Lemma("TEST", 1); var lemma = new Lemma("TEST");
clueMap.put(key, lemma); clueMap.put(key, lemma);
// 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)

View File

@@ -167,8 +167,9 @@ 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(22, res.filled().clueMap().size(), "Number of assigned words changed"); Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words changed");
Assertions.assertEquals(747.5454545454545, res.filled().stats().simplicity, 1e-9, "Simplicity value changed");
Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(515).word()); Assertions.assertEquals(Lemma.pack(new byte[]{ 'I', 'E', 'M', 'A', 'N', 'D', 'S' }), res.filled().clueMap().get(515).word());
Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo);
Assertions.assertEquals(140L, res.filled().grid().grid().hi);
} }
@Test @Test
public void testIsLetterA() { public void testIsLetterA() {

View File

@@ -2,6 +2,7 @@ package puzzle;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import puzzle.Export.IntListDTO;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
@@ -91,7 +92,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testIntList() { void testIntList() {
var list = new IntList(); var list = new IntListDTO();
assertEquals(0, list.size()); assertEquals(0, list.size());
for (var i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
list.add(i); list.add(i);
@@ -103,27 +104,27 @@ public class SwedishGeneratorTest {
@Test @Test
void testLemmaAndDict() { void testLemmaAndDict() {
var l2a = new Lemma("IN", 1); var l2a = new Lemma("IN");
var l4a = new Lemma("INER", 1); var l4a = new Lemma("INER");
var l6a = new Lemma("INEREN", 1); var l6a = new Lemma("INEREN");
var l7a = new Lemma("INERENA", 1); var l7a = new Lemma("INERENA");
var l8a = new Lemma("INERENAE", 1); var l8a = new Lemma("INERENAE");
var l1 = new Lemma("APPLE", 5); var l1 = new Lemma("APPLE");
Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), l1.word()); Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), l1.word());
assertEquals(5, l1.len()); assertEquals(5, l1.len());
assertEquals(5, l1.simpel());
assertEquals((byte) 'A', l1.byteAt(0)); assertEquals((byte) 'A', l1.byteAt(0));
assertEquals(1, l1.intAt(0));
var l2 = new Lemma("AXE", 2); var l2 = new Lemma("AXE");
var dict = new Dict(new Lemma[]{ l1, l2, l2a, l4a, l6a, l7a, l8a }); var dict = new Dict(new Lemma[]{ l1, l2, l2a, l4a, l6a, l7a, l8a });
assertEquals(1, dict.index()[3].words().size()); assertEquals(1, dict.index()[3].words().length);
assertEquals(1, dict.index()[5].words().size()); assertEquals(1, dict.index()[5].words().length);
var entry3 = dict.index()[3]; var entry3 = dict.index()[3];
assertEquals(1, entry3.words().size()); assertEquals(1, entry3.words().length);
assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), entry3.words().getFirst().word()); assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), entry3.words()[0].word());
// 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
@@ -179,15 +180,15 @@ public class SwedishGeneratorTest {
@Test @Test
void testCandidateInfoForPattern() { void testCandidateInfoForPattern() {
var l0 = new Lemma("IN", 1); var l0 = new Lemma("IN");
var l3a = new Lemma("INE", 1); var l3a = new Lemma("INE");
var l4a = new Lemma("INER", 1); var l4a = new Lemma("INER");
var l6a = new Lemma("INEREN", 1); var l6a = new Lemma("INEREN");
var l7a = new Lemma("INERENA", 1); var l7a = new Lemma("INERENA");
var l8a = new Lemma("INERENAE", 1); var l8a = new Lemma("INERENAE");
var l1 = new Lemma("APPLE", 1); var l1 = new Lemma("APPLE");
var l2 = new Lemma("APPLY", 1); var l2 = new Lemma("APPLY");
var l3 = new Lemma("BANAN", 1); var l3 = new Lemma("BANAN");
var dict = new Dict(new Lemma[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); var dict = new Dict(new Lemma[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a });
// Pattern "APP--" for length 5 // Pattern "APP--" for length 5
@@ -274,7 +275,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", 1); var w1 = new Lemma("ABC");
var undoBuffer = new int[10]; var undoBuffer = new int[10];
// 1. Successful placement in empty grid // 1. Successful placement in empty grid
@@ -289,7 +290,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", 1); var w2 = new Lemma("ABD");
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)));
@@ -312,7 +313,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", 1); var w = new Lemma("AZ");
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);