From dd28151d49b2b066b977aa96e10ff7e0d7b1a102 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 12 Jan 2026 02:02:01 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 104 +++++++++++------- src/test/java/puzzle/MainTest.java | 2 +- .../java/puzzle/SwedishGeneratorTest.java | 7 +- 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 052fc19..d65e82e 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -16,9 +16,11 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.stream.IntStream; import static java.nio.charset.StandardCharsets.*; @@ -37,7 +39,7 @@ public record SwedishGenerator(Rng rng) { public CandidateInfo(int n) { this(null, n); } } - static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new); +// static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new); //@formatter:off @FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } @@ -108,7 +110,7 @@ public record SwedishGenerator(Rng rng) { public static record FillResult(boolean ok, Gridded grid, - HashMap clueMap, + Map clueMap, FillStats stats) { public void calcSimpel() { @@ -191,7 +193,7 @@ public record SwedishGenerator(Rng rng) { if (idx < 64) lo |= (1L << idx); else hi |= (1L << (idx & 63)); } - void clear(int idx) { g[idx] = DASH; } + void clearletter(int idx) { g[idx] = DASH; } void clearClue(int idx) { g[idx] = DASH; if (idx < 64) lo &= ~(1L << idx); @@ -246,7 +248,7 @@ public record SwedishGenerator(Rng rng) { boolean hasRoomForClue(long packed) { return (packed & GT_1_OFFSET_53_BIT) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } void forEachSlot(SlotVisitor visitor) { for (var l = lo; l != X; l &= l - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(l)); - for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 | Long.numberOfTrailingZeros(h)); + for (var h = hi; h != X; h &= h - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(h)); } } @@ -352,17 +354,19 @@ public record SwedishGenerator(Rng rng) { static record Slot(int key, long packedPos) { + static final int BIT_FOR_DIR = 3; static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); } - void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clear(pos(i)); } + void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clearletter(pos(i)); } public int len() { return (int) (packedPos >>> 56); } - public int clueR() { return Grid.r((key >>> 4)); } - public int clueIndex() { return key >>> 4; } - public int clueC() { return Grid.c((key >>> 4)); } - public int dir() { return key & 15; } + public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); } + public int clueIndex() { return key >>> BIT_FOR_DIR; } + public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); } + public int dir() { return key & 7; } public boolean horiz() { return horiz(key); } public int pos(int i) { return offset(packedPos, i); } public static boolean horiz(int key) { return (key & 1) == 0/*((key & 15) & 1) == 0*/; } public static int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); } + public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } } private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { @@ -376,7 +380,7 @@ public record SwedishGenerator(Rng rng) { packedPos |= iidx << offset; } if (k > 0) { - visitor.visit((idx << 4) | d, packedPos, k); + visitor.visit(Slot.packSlotDir(idx, d), packedPos, k); } } @@ -410,38 +414,39 @@ public record SwedishGenerator(Rng rng) { 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; + long lo_cl = grid.lo, hi_cl = grid.hi; + long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); + boolean hasSlots = false; + 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 nbrs16 = OFFSETS[d]; - long packed = nbrs16.path()[clueIdx]; - int n = (int) (packed >>> 56) * 7, k, idx; - var horiz = Slot.horiz(d) ? covH : covV; + 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; 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) { + hasSlots = true; if (k < MIN_LEN7) penalty += 8000; - penalty |= 1; } } } - if ((penalty & 1) == 0) return 1_000_000_000L; - penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); + 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 != X; bits &= bits - 1) { - clueIdx = i + Long.numberOfTrailingZeros(bits); + int clueIdx = i + Long.numberOfTrailingZeros(bits); if (seen.get(clueIdx)) continue; int size = 0; stack[0] = clueIdx; @@ -462,8 +467,8 @@ public record SwedishGenerator(Rng rng) { for (int i = 0; i < 65; i += 64) { long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL)); for (; bits != X; bits &= bits - 1) { - clueIdx = i + Long.numberOfTrailingZeros(bits); - var rci = IT[clueIdx]; + int clueIdx = i + Long.numberOfTrailingZeros(bits); + var rci = IT[clueIdx]; if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; var h = covH[clueIdx]; var v = covV[clueIdx]; @@ -601,9 +606,14 @@ public record SwedishGenerator(Rng rng) { if (Main.VERBOSE && (gen & 15) == 15) System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + pop.get(0).fit()); } - - pop.sort(Comparator.comparingLong(GridAndFit::fit)); - return pop.get(0).grid; + GridAndFit best = pop.get(0); + for (int i = 1; i < pop.size(); i++) { + var x = pop.get(i); + if (x.fit() < best.fit()) best = x; + } + //pop.sort(Comparator.comparingLong(GridAndFit::fit)); + //return pop.get(0).grid; + return best.grid; } static void patternForSlot(Grid grid, Slot s, byte[] pat) { byte ch; @@ -631,7 +641,7 @@ public record SwedishGenerator(Rng rng) { } else if (cur != ch) { for (var j = 0; j < i; j++) { if ((mask & (1 << j)) != 0) { - grid.clear(s.pos(j)); + grid.clearletter(s.pos(j)); } } return false; @@ -655,7 +665,8 @@ public record SwedishGenerator(Rng rng) { } if (listCount == 0) { - return CANDIDATES[entry.words.length]; + //return CANDIDATES[entry.words.length]; + return new CandidateInfo(null, entry.words.length); } // Sort constraints by size to optimize intersection @@ -693,9 +704,10 @@ public record SwedishGenerator(Rng rng) { val multiThreaded = Thread.currentThread().getName().contains("pool"); val grid = mask.deepCopyGrid(); val used = new Bit1029(); - val assigned = new HashMap(); - val ctx = CTX.get(); - val count = ctx.cellCount; + // val assigned = new HashMap(); + Lemma[] assigned = new Lemma[1024]; + val ctx = CTX.get(); + val count = ctx.cellCount; Arrays.fill(count, 0, SIZE, 0); val slots = extractSlots(grid); @@ -716,8 +728,10 @@ public record SwedishGenerator(Rng rng) { var now = System.currentTimeMillis(); if ((now - lastLog) < LOG_EVERY_MS) return; lastLog = (now); - - var done = assigned.size(); + var done = 0; + for (var lemma : assigned) { + if (lemma != null) done++; + } 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 bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]"; @@ -737,7 +751,7 @@ public record SwedishGenerator(Rng rng) { int bestScore = -1; for (int i = 0, n = slots.size(); i < n; i++) { var s = slots.get(i); - if (assigned.containsKey(s.key())) continue; + if (assigned[s.key()] != null) continue; var entry = dictIndex[s.len()]; if (entry == null) return PICK_NOT_DONE; patternForSlot(grid, s, ctx.pattern); @@ -798,11 +812,11 @@ public record SwedishGenerator(Rng rng) { if (!placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index); - assigned.put(k, w); + assigned[k] = w; if (backtrack(depth + 1)) return true; - assigned.remove(k); + assigned[k] = null; used.clear(w.index); s.undoPlace(grid, ctx.undo[depth]); } @@ -827,11 +841,11 @@ public record SwedishGenerator(Rng rng) { if (!placeWord(grid, s, w, ctx.undo, depth)) continue; used.set(w.index); - assigned.put(k, w); + assigned[k] = w; if (backtrack(depth + 1)) return true; - assigned.remove(k); + assigned[k] = null; used.clear(w.index); s.undoPlace(grid, ctx.undo[depth]); } @@ -852,14 +866,20 @@ public record SwedishGenerator(Rng rng) { } stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; - var res = new FillResult(ok, new Gridded(grid), assigned, stats); + Map lemmaMap = new HashMap<>(); + for (var i = 0; i < assigned.length; i++) { + if (assigned[i] != null) { + lemmaMap.put(i, assigned[i]); + } + } + var res = new FillResult(ok, new Gridded(grid), lemmaMap, stats); // print a final progress line if (Main.VERBOSE && !multiThreaded) { System.out.println( String.format(Locale.ROOT, "[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs", - assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds + lemmaMap.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds ) ); } diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 1de566a..bef492a 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -167,7 +167,7 @@ public class MainTest { // Regression baseline for seed search starting at 12347, pop 4, gens 20 Assertions.assertEquals(12348, foundSeed, "Found seed changed"); Assertions.assertEquals(22, res.filled().clueMap().size(), "Number of assigned words 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(259).word()); Assertions.assertEquals(648985643903632391L, res.filled().grid().grid().lo); Assertions.assertEquals(140L, res.filled().grid().grid().hi); } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 81c40d6..a39f95c 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -135,8 +135,12 @@ public class SwedishGeneratorTest { @Test void testSlot() { + System.out.println("[DEBUG_LOG] Slot.BIT_FOR_DIR = " + Slot.BIT_FOR_DIR); // key = (r << 8) | (c << 4) | d - var key = (Grid.offset(2, 3) << 4) | 5; + var offset = Grid.offset(2, 3); + System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset); + var key = (offset << Slot.BIT_FOR_DIR) | 5; + System.out.println("[DEBUG_LOG] key = " + key); long packedPos = 0; // pos 0: (2, 5) packedPos |= Grid.offset(2, 5); @@ -146,6 +150,7 @@ public class SwedishGeneratorTest { packedPos |= (long) Grid.offset(4, 5) << 14; var s = Slot.from(key, packedPos, 3); + System.out.println("[DEBUG_LOG] s.dir() = " + s.dir()); assertEquals(2, s.clueR()); assertEquals(3, s.clueC()); assertEquals(5, s.dir());