From 9a7a7e4477209bff30016e2a40cc043d4d3018f0 Mon Sep 17 00:00:00 2001 From: mike Date: Sat, 10 Jan 2026 15:59:19 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 228 ++++++++++----------- 1 file changed, 107 insertions(+), 121 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 7b1f8bb..94f20b7 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -153,56 +153,55 @@ public record SwedishGenerator(Rng rng) { double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } } - record Grid(byte[] g, long[] bo) { + static class Grid { + final byte[] g; + long bo0, bo1; - public Grid(byte[] g) { this(g, new long[2]); } - static Grid createEmpty() { return new Grid(new byte[SIZE], new long[2]); } + public Grid(byte[] g) { this(g, 0, 0); } + public Grid(byte[] g, long bo0, long bo1) { this.g = g; this.bo0 = bo0; this.bo1 = bo1; } + static Grid createEmpty() { return new Grid(new byte[SIZE], 0L, 0L); } int digitAt(int index) { return g[index] - 48; } 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); } - Grid deepCopyGrid() { return new Grid(g.clone(), bo.clone()); } + Grid deepCopyGrid() { return new Grid(g.clone(), bo0, bo1); } public byte byteAt(int pos) { return g[pos]; } void setByteAt(int idx, byte ch) { g[idx] = ch; } void setClue(int idx, byte ch) { g[idx] = ch; - if (idx < 64) bo[0] |= (1L << idx); - else bo[1] |= (1L << (idx & 63)); + if (idx < 64) bo0 |= (1L << idx); + else bo1 |= (1L << (idx & 63)); } void clear(int idx) { g[idx] = DASH; } void clearClue(int idx) { g[idx] = DASH; - if (idx < 64) bo[0] &= ~(1L << idx); - else bo[1] &= ~(1L << (idx & 63)); + if (idx < 64) bo0 &= ~(1L << idx); + else bo1 &= ~(1L << (idx & 63)); } static boolean isDigit(byte b) { return (b & 48) == 48; } boolean isDigitAt(int index) { return isDigit(g[index]); } boolean isClue(int index) { - if (index < 64) { - return ((bo[0] >> index) & 1L) != 0; - } - return ((bo[1] >> (index & 63)) & 1L) != 0; + if (index < 64) return ((bo0 >> index) & 1L) != 0; + return ((bo1 >> (index & 63)) & 1L) != 0; } boolean notClue(int index) { - if (index < 64) { - return ((bo[0] >> index) & 1L) == 0L; - } - return ((bo[1] >> (index & 63)) & 1L) == 0L; + if (index < 64) return ((bo0 >> index) & 1L) == 0L; + return ((bo1 >> (index & 63)) & 1L) == 0L; } boolean clueless(int idx) { if (idx < 64) { val test = (1L << idx); - if ((test & bo[0]) != 0L) { + if ((test & bo0) != 0L) { g[idx] = DASH; - bo[0] &= ~test; + bo0 &= ~test; return true; } return false; } else { val test = (1L << (idx & 63)); - if ((test & bo[1]) != 0L) { + if ((test & bo1) != 0L) { g[idx] = DASH; - bo[1] &= ~test; + bo1 &= ~test; return true; } return false; @@ -218,11 +217,11 @@ public record SwedishGenerator(Rng rng) { for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; return same / SIZED; } - int clueCount() { return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); } + int clueCount() { return Long.bitCount(bo0) + Long.bitCount(bo1); } void forEachSlot(SlotVisitor visitor) { - for (var lo = bo[0]; lo != 0L; lo &= lo - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(lo)); - for (var hi = bo[1]; hi != 0L; hi &= hi - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(hi)); + for (var lo = bo0; lo != 0L; lo &= lo - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(lo)); + for (var hi = bo1; hi != 0L; hi &= hi - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(hi)); } } @@ -334,6 +333,23 @@ public record SwedishGenerator(Rng rng) { } static int intersectSorted(int[] a, int aLen, int[] b, int bLen, int[] out) { + if (aLen == 0 || bLen == 0) return 0; + if (aLen < bLen >>> 4) { + int k = 0; + for (int i = 0; i < aLen; i++) { + int x = a[i]; + if (Arrays.binarySearch(b, 0, bLen, x) >= 0) out[k++] = x; + } + return k; + } + if (bLen < aLen >>> 4) { + int k = 0; + for (int i = 0; i < bLen; i++) { + int y = b[i]; + if (Arrays.binarySearch(a, 0, aLen, y) >= 0) out[k++] = y; + } + return k; + } int i = 0, j = 0, k = 0, x, y; while (i < aLen && j < bLen) { x = a[i]; @@ -393,92 +409,78 @@ public record SwedishGenerator(Rng rng) { } 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); - long lo_cl = grid.bo[0], hi_cl = grid.bo[1]; + long lo_cl = grid.bo0, hi_cl = grid.bo1; + long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); + boolean hasSlots = false; - // clue clustering (8-connected) - var seen = ctx.seen; - var stack = ctx.stack; - class Mask { - - long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); - boolean hasSlots = false; - void space(int clueIdx) { - var d = grid.digitAt(clueIdx); - var nbrs16 = OFFSETS[d]; + for (int i = 0; i < 65; i+=64) { + for (long bits = (i == 0 ? lo_cl : hi_cl); bits != 0L; bits &= bits - 1) { + int clueIdx = i + Long.numberOfTrailingZeros(bits); + var d = grid.digitAt(clueIdx); + var nbrs16 = OFFSETS[d]; long packed = nbrs16.path()[clueIdx]; - int n = (int) (packed >>> 56) * 7, k, idx; - var horiz = Slot.horiz(d) ? covH : covV; + int n = (int) (packed >>> 56) * 7, k, idx; + var horiz = Slot.horiz(d) ? covH : covV; for (k = 0; k < n && k < MAX_WORD_LENGTH7; k += 7) { idx = (int) ((packed >>> (k)) & 0x7F); if (grid.isClue(idx)) break; horiz[idx] += 1; } - if (k == 0) return; - hasSlots = true; - if (k < MIN_LEN7) penalty += 8000; + if (k > 0) { + hasSlots = true; + if (k < MIN_LEN7) penalty += 8000; + } } - - void clueStackPenalty(int clueIdx) { - if (seen.get(clueIdx)) return; - int size = 0; - long packed; + } + + if (!hasSlots) return 1_000_000_000L; + + var seen = ctx.seen; + var stack = ctx.stack; + seen.clear(); + + for (int i = 0; i < 65; i+=64) { + for (long bits = (i == 0 ? lo_cl : hi_cl); bits != 0L; bits &= bits - 1) { + int clueIdx = i + Long.numberOfTrailingZeros(bits); + if (seen.get(clueIdx)) continue; + int size = 0; stack[0] = clueIdx; seen.set(clueIdx); - - for (int sp = 1, n, nidx, k; sp > 0; size++) { - packed = Neighbors9x8.NBR8_PACKED[stack[--sp]]; - n = (int) (packed >>> 56) * 7; - for (k = 0; k < n; k += 7) { - nidx = (int) ((packed >>> k) & 0x7F); + for (int sp = 1; sp > 0; size++) { + long packed = Neighbors9x8.NBR8_PACKED[stack[--sp]]; + int n = (int) (packed >>> 56) * 7; + for (int k = 0; k < n; k += 7) { + int nidx = (int) ((packed >>> k) & 0x7F); if (seen.get(nidx) || grid.notClue(nidx)) continue; seen.set(nidx); stack[sp++] = nidx; } } - if (size >= 2) this.penalty += ((size - 1L) * 120L); + if (size >= 2) penalty += ((size - 1L) * 120L); } - void wall(rci rci) { - if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) this.penalty += 400; - var h = covH[rci.i()]; - var v = covV[rci.i()]; - if (h == 0 && v == 0) this.penalty += 1500; - else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; - else this.penalty += 600; - } - } - val space = new Mask(); - for (var lo = lo_cl; lo != 0L; lo &= lo - 1) space.space(Long.numberOfTrailingZeros(lo)); - for (var hi = hi_cl; hi != 0L; hi &= hi - 1) space.space(64 + Long.numberOfTrailingZeros(hi)); - if (!space.hasSlots) return 1_000_000_000L; - seen.clear(); - for (var lo = lo_cl; lo != 0L; lo &= lo - 1) space.clueStackPenalty(Long.numberOfTrailingZeros(lo)); - for (var hi = hi_cl; hi != 0L; hi &= hi - 1) space.clueStackPenalty(64 + Long.numberOfTrailingZeros(hi)); - for (var lo = ~lo_cl; lo != 0L; lo &= lo - 1) space.wall(IT[Long.numberOfTrailingZeros(lo)]); - for (var hi = ~hi_cl & 0xFFL; hi != 0L; hi &= hi - 1) space.wall(IT[64 + Long.numberOfTrailingZeros(hi)]); - //for (var rci : IT) if (grid.notClue(rci.i())) space.wall(rci); - // dead-end-ish letter cell (3+ walls) - // int walls, wc, wr; - /* for (var rci : IT) { - if (grid.isDigitAt(rci.i())) continue; - walls = 0; - for (var d : nbrs4) { - wr = rci.r() + d.r(); - wc = rci.c() + d.c(); - if (wr < 0 || wr >= R || wc < 0 || wc >= C || grid.isDigitAt(Grid.offset(wr, wc))) walls++; + for (int i = 0; i < 65; i+=64) { + long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL)); + for (; bits != 0L; bits &= bits - 1) { + int idx = i + Long.numberOfTrailingZeros(bits); + var rci = IT[idx]; + if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; + var h = covH[idx]; + var v = covV[idx]; + if (h == 0 && v == 0) penalty += 1500; + else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200; + else penalty += 600; } - if (walls >= 3) penalty[0] += 400; - }*/ + } - return space.penalty; + return penalty; } Grid randomMask() { @@ -516,7 +518,7 @@ public record SwedishGenerator(Rng rng) { var nc = Math.cos(theta); var nr = Math.sin(theta); - long bo0 = out.bo[0], bo1 = out.bo[1]; + long bo0 = out.bo0, bo1 = out.bo1; for (var rci : IT) { int i = rci.i(); if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { @@ -533,10 +535,10 @@ public record SwedishGenerator(Rng rng) { } } } - out.bo[0] = bo0; - out.bo[1] = bo1; - for (var lo = out.bo[0]; lo != 0L; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.bo[1]; hi != 0L; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); + out.bo0 = bo0; + out.bo1 = bo1; + for (var lo = out.bo0; lo != 0L; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); + for (var hi = out.bo1; hi != 0L; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); return out; } public static void clearClues(Grid out, int idx) { if (!hasRoomForClue(out, OFFSETS[out.digitAt(idx)].path()[idx])) out.clearClue(idx); } @@ -564,10 +566,10 @@ public record SwedishGenerator(Rng rng) { class GridAndFit { Grid grid; - Long fite; + long fite = -1; GridAndFit(Grid grid) { this.grid = grid; } long fit() { - if (fite == null) this.fite = maskFitness(grid); + if (fite == -1) this.fite = maskFitness(grid); return this.fite; } } @@ -707,6 +709,9 @@ public record SwedishGenerator(Rng rng) { val slots = extractSlots(grid); for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; + val slotScores = new int[slots.size()]; + for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i)); + val t0 = System.currentTimeMillis(); val stats = new FillStats(); val TOTAL = slots.size(); @@ -737,8 +742,9 @@ public record SwedishGenerator(Rng rng) { Pick chooseMRV() { Slot best = null; CandidateInfo bestInfo = null; - int bestSlot = -1; - for (var s : slots) { + int bestScore = -1; + for (int i = 0, n = slots.size(); i < n; i++) { + var s = slots.get(i); if (assigned.containsKey(s.key())) continue; var entry = dictIndex[s.len()]; if (entry == null) return PICK_NOT_DONE; @@ -746,12 +752,11 @@ public record SwedishGenerator(Rng rng) { var info = candidateInfoForPattern(ctx, entry, s.len()); if (info.count == 0) return PICK_NOT_DONE; - var slotScore = -1; if (best == null || info.count < bestInfo.count - || (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) { - best = s; - bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s); + || (info.count == bestInfo.count && slotScores[i] > bestScore)) { + best = s; + bestScore = slotScores[i]; if (info.indices != null && (info.indices == ctx.inter1 || info.indices == ctx.inter2)) { bestInfo = new CandidateInfo(Arrays.copyOf(info.indices, info.count), info.count); } else { @@ -761,11 +766,8 @@ public record SwedishGenerator(Rng rng) { } } - if (best == null) { - return PICK_DONE; - } else { - return new Pick(best, bestInfo, false); - } + if (best == null) return PICK_DONE; + return new Pick(best, bestInfo, false); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; @@ -787,8 +789,7 @@ public record SwedishGenerator(Rng rng) { var k = s.key(); int patLen = s.len(); var entry = dictIndex[patLen]; - var pat = new byte[patLen]; - patternForSlot(grid, s, pat); + if (info.indices != null && info.indices.length > 0) { var idxs = info.indices; var L = idxs.length; @@ -802,15 +803,7 @@ public record SwedishGenerator(Rng rng) { if (used.get(w.index())) continue; - boolean match = true; - for (var i = 0; i < patLen; i++) { - if (pat[i] != DASH && pat[i] != w.byteAt(i)) { - match = false; - break; - } - } - - if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; + if (!placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index()); assigned.put(k, w); @@ -818,7 +811,7 @@ public record SwedishGenerator(Rng rng) { if (backtrack(depth + 1)) return true; assigned.remove(k); - used.clear(w.index); + used.clear(w.index()); s.undoPlace(grid, ctx.undo[depth]); } stats.backtracks++; @@ -839,14 +832,7 @@ public record SwedishGenerator(Rng rng) { if (used.get(w.index())) continue; - boolean match = true; - for (var i = 0; i < patLen; i++) { - if (pat[i] != DASH && pat[i] != w.byteAt(i)) { - match = false; - break; - } - } - if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue; + if (!placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index()); assigned.put(k, w);