diff --git a/pom.xml b/pom.xml
index 55c38ee..189a633 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,6 +41,12 @@
2.0.13
runtime
+
+ org.junit.jupiter
+ junit-jupiter
+ RELEASE
+ test
+
diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java
index 67d6c9a..bc54662 100644
--- a/src/main/java/puzzle/SwedishGenerator.java
+++ b/src/main/java/puzzle/SwedishGenerator.java
@@ -78,9 +78,11 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
int[] covV,
int[] cellCount,
int[] stack,
- BitSet seen) {
+ BitSet seen,
+ char[] pattern,
+ IntList[] intListBuffer) {
- public Context() { this(new int[256], new int[256], new int[256], new int[256], new BitSet(256)); }
+ public Context() { this(new int[256], new int[256], new int[256], new int[256], new BitSet(256), new char[32], new IntList[32]); }
}
static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new);
@@ -278,28 +280,28 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
return Arrays.copyOf(buff, k);
}
- CandidateInfo candidateInfoForPattern(DictEntry entry, char[] pattern) {
- var lists = new ArrayList();
- for (var i = 0; i < pattern.length; i++) {
+ CandidateInfo candidateInfoForPattern(DictEntry entry, char[] pattern, int len) {
+ var ctx = CTX.get();
+ var listBuffer = ctx.intListBuffer;
+ int listCount = 0;
+ for (var i = 0; i < len; i++) {
var ch = pattern[i];
if (isLetter(ch)) {
- lists.add(entry.pos[i][ch - 'A']);
+ listBuffer[listCount++] = entry.pos[i][ch - 'A'];
}
}
- if (lists.isEmpty()) {
+ if (listCount == 0) {
return new CandidateInfo(null, entry.words.size());
}
- var first = lists.get(0);
- var cur = first.data();//Arrays.copyOf(first.data(), first.size());
- var curLen = cur.length;
+ var first = listBuffer[0];
+ var cur = first.data();
+ var curLen = first.size();
- for (var k = 1; k < lists.size(); k++) {
- var nxt = lists.get(k);
- var nextArr = nxt.data();
- var nextLen = nxt.size();
- cur = intersectSorted(buff, cur, curLen, nextArr, nextLen);
+ for (var k = 1; k < listCount; k++) {
+ var nxt = listBuffer[k];
+ cur = intersectSorted(buff, cur, curLen, nxt.data(), nxt.size());
curLen = cur.length;
if (curLen == 0) break;
}
@@ -312,13 +314,22 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
public int clueR() { return (key >> 8) & 15; }
public int clueC() { return (key >> 4) & 15; }
public int dir() { return key & 15; }
- public boolean horiz() { return (dir() & 1) == 0; }
- public int r(int i) { return (int) ((rs >> (i << 2)) & 15); }
- public int c(int i) { return (int) ((cs >> (i << 2)) & 15); }
+ public boolean horiz() { return horiz(key); }
+ public int r(int i) { return r(rs, i); }
+ public int c(int i) { return c(cs, i); }
+
+ public static boolean horiz(int key) { return ((key & 15) & 1) == 0; }
+ public static int r(long rs, int i) { return (int) ((rs >> (i << 2)) & 15); }
+ public static int c(long cs, int i) { return (int) ((cs >> (i << 2)) & 15); }
}
- ArrayList extractSlots(Grid grid) {
- var slots = new ArrayList(64);
+ @FunctionalInterface
+ interface SlotVisitor {
+
+ void visit(int key, long rs, long cs, int len);
+ }
+
+ void forEachSlot(Grid grid, SlotVisitor visitor) {
for (var r = 0; r < H; r++) {
for (var c = 0; c < W; c++) {
if (!grid.isDigitAt(r, c)) continue;
@@ -343,9 +354,16 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
cc += dc;
if (n >= 12) break;
}
- slots.add(new Slot((r << 8) | (c << 4) | d, packedRs, packedCs,n ));
+ if (n > 0) {
+ visitor.visit((r << 8) | (c << 4) | d, packedRs, packedCs, n);
+ }
}
}
+ }
+
+ ArrayList extractSlots(Grid grid) {
+ var slots = new ArrayList(64);
+ forEachSlot(grid, (key, rs, cs, len) -> slots.add(new Slot(key, rs, cs, len)));
return slots;
}
@@ -372,35 +390,59 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
var targetClues = (int) Math.round(SIZE * 0.25); // ~18
penalty += 8L * Math.abs(clueCount - targetClues);
- var slots = extractSlots(grid);
- if (slots.isEmpty()) return 1_000_000_000L;
-
var ctx = CTX.get();
var covH = ctx.covH;
var covV = ctx.covV;
Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0);
- for (var s : slots) {
- if (s.len() < MIN_LEN) {
- penalty += 8000;
- }
- /* else if (s.len > MAX_LEN) {
- penalty += 8000 + (long) (s.len - MAX_LEN) * 500L;
- throw new RuntimeException();
- }*/
- else {
- if (lenCounts[s.len()] <= 0) penalty += 12000;
- }
-
- for (var i = 0; i < s.len(); i++) {
- int r = s.r(i), c = s.c(i);
- int idx = grid.offset(r, c);
- if (s.horiz()) covH[idx] += 1;
- else covV[idx] += 1;
+ boolean hasSlots = false;
+ for (var r = 0; r < H; r++) {
+ for (var c = 0; c < W; c++) {
+ if (!grid.isDigitAt(r, c)) continue;
+ var d = grid.digitAt(r, c);
+ int or = OFFSETS[d].x, oc = OFFSETS[d].y;
+ int dr = STEPS[d].x, dc = STEPS[d].y;
+
+ int rr = r + or, cc = c + oc;
+ if (rr < 0 || rr >= H || cc < 0 || cc >= W) continue;
+ if (grid.isDigitAt(rr, cc)) continue;
+
+ long packedRs = 0;
+ long packedCs = 0;
+ var n = 0;
+
+ while (rr >= 0 && rr < H && cc >= 0 && cc < W) {
+ if (grid.isDigitAt(rr, cc)) break;
+ packedRs |= (long) rr << (n << 2);
+ packedCs |= (long) cc << (n << 2);
+ n++;
+ rr += dr;
+ cc += dc;
+ if (n >= 12) break;
+ }
+ if (n == 0) continue;
+ hasSlots = true;
+
+ if (n < MIN_LEN) {
+ penalty += 8000;
+ } else {
+ if (lenCounts[n] <= 0) penalty += 12000;
+ }
+
+ boolean horiz = Slot.horiz((r << 8) | (c << 4) | d);
+ for (var i = 0; i < n; i++) {
+ int sr = Slot.r(packedRs, i);
+ int sc = Slot.c(packedCs, i);
+ int idx = grid.offset(sr, sc);
+ if (horiz) covH[idx] += 1;
+ else covV[idx] += 1;
+ }
}
}
+ if (!hasSlots) return 1_000_000_000L;
+
for (var r = 0; r < H; r++)
for (var c = 0; c < W; c++) {
if (grid.isDigitAt(r, c)) continue;
@@ -636,13 +678,12 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
}
}
- static char[] patternForSlot(Grid grid, Slot s) {
- var pat = new char[s.len()];
+ static int patternForSlot(Grid grid, Slot s, char[] pat) {
for (var i = 0; i < s.len(); i++) {
var ch = grid.getCharAt(s.r(i), s.c(i));
- if (isLetter(ch)) pat[i] = ch;
+ pat[i] = isLetter(ch) ? ch : C_DASH;
}
- return pat;
+ return s.len();
}
static int slotScore(int[] cellCount, Slot s, Grid grid) {
@@ -733,8 +774,8 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
return new Pick(null, null, false);
}
- var pat = patternForSlot(grid, s);
- var info = candidateInfoForPattern(entry, pat);
+ var patLen = patternForSlot(grid, s, ctx.pattern);
+ var info = candidateInfoForPattern(entry, ctx.pattern, patLen);
if (info.count == 0) {
return new Pick(null, null, false);
@@ -778,29 +819,8 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
var s = pick.slot;
var k = s.key();
var entry = dictIndex[s.len()];
- var pat = patternForSlot(grid, s);
-
- Predicate tryWord = (Lemma w) -> {
- if (used.get(w.index())) return false;
-
- for (var i = 0; i < pat.length; i++) {
- // if ((pat[i] != w.charAt(i)) != (pat[i] != C_DASH)) throw new RuntimeException("word does not match pattern");
- if (pat[i] != C_DASH && pat[i] != w.charAt(i)) return false;
- }
-
- var undo = placeWord(grid, s, w);
- if (undo == null) return false;
-
- used.set(w.index());
- assigned.put(k, w);
-
- if (backtrack()) return true;
-
- assigned.remove(k);
- used.set(w.index, false);
- undoPlace(grid, undo);
- return false;
- };
+ var pat = new char[s.len()];
+ patternForSlot(grid, s, pat);
if (pick.info.indices != null && pick.info.indices.length > 0) {
var idxs = pick.info.indices;
@@ -815,7 +835,29 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
int idxInArray = (int) (r * r * r * L);
var idx = idxs[idxInArray];
var w = entry.words.get(idx);
- if (tryWord.test(w)) return true;
+
+ if (used.get(w.index())) continue;
+
+ boolean match = true;
+ for (var i = 0; i < pat.length; i++) {
+ if (pat[i] != C_DASH && pat[i] != w.charAt(i)) {
+ match = false;
+ break;
+ }
+ }
+ if (!match) continue;
+
+ var undo = placeWord(grid, s, w);
+ if (undo == null) continue;
+
+ used.set(w.index());
+ assigned.put(k, w);
+
+ if (backtrack()) return true;
+
+ assigned.remove(k);
+ used.set(w.index, false);
+ undoPlace(grid, undo);
}
stats.backtracks++;
return false;
@@ -832,7 +874,29 @@ public record SwedishGenerator(int W, int H, int SIZE, int MAX_LEN, int[] buff)
double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * N);
var w = entry.words.get(idxInArray);
- if (tryWord.test(w)) return true;
+
+ if (used.get(w.index())) continue;
+
+ boolean match = true;
+ for (var i = 0; i < pat.length; i++) {
+ if (pat[i] != C_DASH && pat[i] != w.charAt(i)) {
+ match = false;
+ break;
+ }
+ }
+ if (!match) continue;
+
+ var undo = placeWord(grid, s, w);
+ if (undo == null) continue;
+
+ used.set(w.index());
+ assigned.put(k, w);
+
+ if (backtrack()) return true;
+
+ assigned.remove(k);
+ used.set(w.index, false);
+ undoPlace(grid, undo);
}
stats.backtracks++;