diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 245fbdd..b7bfd4c 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -179,8 +179,8 @@ public record Export() { var placed = new ArrayList(); var clueMap = filled().clueMap(); g.grid().forEachSlot((int key, long lo, long hi) -> { - var word = clueMap.get(key); - if (word != null) { + var word = clueMap[key]; + if (word != 0L) { placed.add(extractPlacedFromSlot(Slot.from(key, lo, hi), word)); } else { System.err.println("Could not find clue for slot: " + key); diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 831a585..3e47b93 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -41,7 +41,7 @@ public class Main { public int seed = (int) (System.nanoTime() ^ System.currentTimeMillis()); public int pop = 18; - public int gens = 1000; + public int gens = 1200; public String wordsPath = "nl_score_hints_v3.csv"; public double minSimplicity = 0; // 0 means no limit public int threads = Math.max(1, Runtime.getRuntime().availableProcessors()); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 3347dfe..74b3410 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -31,8 +31,6 @@ import static java.nio.charset.StandardCharsets.*; public record SwedishGenerator(Rng rng) { record CandidateInfo(int[] indices, int count) { } - -// 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 lo, long hi); } @@ -100,22 +98,35 @@ public record SwedishGenerator(Rng rng) { public static record FillResult(boolean ok, Gridded grid, - Map clueMap, + long[] clueMap, FillStats stats) { public void calcSimpel() { if (ok) { - clueMap.forEach((k, v) -> stats.simplicity += Lemma.simpel(v)); - stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size(); + int k = 0; + for (var n = 1; n < clueMap.length; n++) { + if (clueMap[n] != 0L) { + k++; + stats.simplicity += Lemma.simpel(clueMap[n]); + } + } + stats.simplicity = k == 0 ? 0 : stats.simplicity / k; } } - + public int wordCount() { + int k = 0; + for (var n = 1; n < clueMap.length; n++) { + if (clueMap[n] != 0L) { + k++; + } + } + return k; + } } static final class Context { - final int[] cellCount = new int[SIZE]; - final int[] stack = new int[SIZE]; + final int[] stack = new int[SIZE]; long pattern; final long[] undo = new long[128]; final long[] bitset = new long[2500]; @@ -245,7 +256,6 @@ public record SwedishGenerator(Rng rng) { static String[] clue(long w) { return CsvIndexService.clues(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 usedCharsInPattern(long p) { return ((63 - Long.numberOfLeadingZeros(p & LETTER_MASK)) / 5) + 1; } public static String asWord(long word) { var b = new byte[Lemma.length(word)]; for (var i = 0; i < b.length; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64); @@ -325,8 +335,7 @@ public record SwedishGenerator(Rng rng) { } private static void processSlot(Grid grid, SlotVisitor visitor, int idx) { - int d = grid.digitAt(idx); // 0..3 - int key = Slot.packSlotDir(idx, d); + int key = Slot.packSlotDir(idx, grid.digitAt(idx)); // 0..3 long rayLo = PATH_LO[key]; long rayHi = PATH_HI[key]; @@ -338,7 +347,7 @@ public record SwedishGenerator(Rng rng) { // slice ray to stop before first clue, depending on direction monotonicity // right/down => increasing indices; up/left => decreasing indices - if (Slot.increasing(d)) { + if (Slot.increasing(key)) { // first clue is lowest index among hits (lo first, then hi) if (hitsLo != 0) { long stop = 1L << Long.numberOfTrailingZeros(hitsLo); @@ -375,7 +384,7 @@ public record SwedishGenerator(Rng rng) { } long maskFitness(Grid grid) { - var ctx = CTX.get(); + long cHLo = 0L, cHHi = 0L, cVLo = 0L, cVHi = 0L; long lo_cl = grid.lo, hi_cl = grid.hi; long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) * 16000L); @@ -384,11 +393,10 @@ public record SwedishGenerator(Rng rng) { for (int i = 0; i < 65; i += 64) { for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { int clueIdx = i | Long.numberOfTrailingZeros(bits); - int d = grid.digitAt(clueIdx); - int key = (clueIdx << 2) | d; + int key = Slot.packSlotDir(clueIdx, grid.digitAt(clueIdx)); long rLo = PATH_LO[key], rHi = PATH_HI[key]; long hLo = rLo & lo_cl, hHi = rHi & hi_cl; - if (Slot.increasing(d)) { + if (Slot.increasing(key)) { if (hLo != 0) { rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1); rHi = 0; @@ -405,7 +413,7 @@ public record SwedishGenerator(Rng rng) { } if ((rLo | rHi) != 0) { hasSlots = true; - if (Slot.horiz(d)) { + if (Slot.horiz(key)) { cHLo |= rLo; cHHi |= rHi; } else { @@ -420,7 +428,7 @@ public record SwedishGenerator(Rng rng) { } if (!hasSlots) return 1_000_000_000L; - + var ctx = CTX.get(); var stack = ctx.stack; long seenLo = 0L, seenHi = 0L; @@ -512,7 +520,7 @@ public record SwedishGenerator(Rng rng) { idx = rng.randint(0, SIZE_MIN_1); if (g.isClue(idx)) continue; var d_idx = rng.randint2bitByte(); - if (g.hasRoomForClue(OFFSETS_D_IDX[d_idx | idx << 2])) { + if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, d_idx)])) { g.setClue(idx, d_idx); placed++; } @@ -527,7 +535,7 @@ public record SwedishGenerator(Rng rng) { ri = bytes[rng.randint(0, 624)]; if (!g.clueless(ri)) { var d_idx = rng.randint2bitByte(); - val packed = OFFSETS_D_IDX[d_idx | ri << 2]; + val packed = OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)]; if (g.hasRoomForClue(packed)) g.setClue(ri, d_idx); } } @@ -562,7 +570,7 @@ public record SwedishGenerator(Rng rng) { for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 | Long.numberOfTrailingZeros(hi)); return out; } - public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[(out.digitAt(idx)) | (idx << 2)])) out.clearClue(idx); } + public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(idx, out.digitAt(idx))])) out.clearClue(idx); } Grid hillclimb(Grid start, int limit) { var best = start; @@ -644,26 +652,38 @@ public record SwedishGenerator(Rng rng) { if (s.increasing()) { for (long b = s.lo & ~grid.lo; b != 0; b &= b - 1) { int idx = Long.numberOfTrailingZeros(b); - int i = Long.bitCount(s.lo & ((1L << idx) - 1)); - p |= ((long) (grid.g[idx] & 31)) << (i * 5); + int val = grid.g[idx] & 31; + if (val != 0) { + int i = Long.bitCount(s.lo & ((1L << idx) - 1)); + p |= ((long) (i * 26 + val)) << (i << 3); + } } int offset = Long.bitCount(s.lo); for (long b = s.hi & ~grid.hi; b != 0; b &= b - 1) { int idx = Long.numberOfTrailingZeros(b); - int i = offset + Long.bitCount(s.hi & ((1L << idx) - 1)); - p |= ((long) (grid.g[64 | idx] & 31)) << (i * 5); + int val = grid.g[64 | idx] & 31; + if (val != 0) { + int i = offset + Long.bitCount(s.hi & ((1L << idx) - 1)); + p |= ((long) (i * 26 + val)) << (i << 3); + } } } else { int offset = Long.bitCount(s.hi); for (long b = s.hi & ~grid.hi; b != 0; b &= b - 1) { int idx = Long.numberOfTrailingZeros(b); - int i = Long.bitCount(s.hi & ~((1L << idx) | ((1L << idx) - 1))); - p |= ((long) (grid.g[64 | idx] & 31)) << (i * 5); + int val = grid.g[64 | idx] & 31; + if (val != 0) { + int i = Long.bitCount(s.hi & ~((1L << idx) | ((1L << idx) - 1))); + p |= ((long) (i * 26 + val)) << (i << 3); + } } for (long b = s.lo & ~grid.lo; b != 0; b &= b - 1) { int idx = Long.numberOfTrailingZeros(b); - int i = offset + Long.bitCount(s.lo & ~((1L << idx) | ((1L << idx) - 1))); - p |= ((long) (grid.g[idx] & 31)) << (i * 5); + int val = grid.g[idx] & 31; + if (val != 0) { + int i = offset + Long.bitCount(s.lo & ~((1L << idx) | ((1L << idx) - 1))); + p |= ((long) (i * 26 + val)) << (i << 3); + } } } return p; @@ -743,16 +763,19 @@ public record SwedishGenerator(Rng rng) { long[] res = ctx.bitset; boolean first = true; - for (int i = 0, len = lenb/*Lemma.usedCharsInPattern(pattern)*/; i < len; i++) { - int val = (int) ((pattern >>> (i * 5)) & 31); - if (val != 0) { - long[] bs = entry.posBitsets[i * 26 + (val - 1)]; + for (long p = pattern; p != 0; ) { + int combined = (int) (p & 0xFF); + if (combined != 0) { + long[] bs = entry.posBitsets[combined - 1]; if (first) { System.arraycopy(bs, 0, res, 0, numLongs); first = false; } else { for (int k = 0; k < numLongs; k++) res[k] &= bs[k]; } + p >>>= 8; + } else { + p >>>= (Long.numberOfTrailingZeros(p) & ~7); } } @@ -783,16 +806,19 @@ public record SwedishGenerator(Rng rng) { long[] res = ctx.bitset; boolean first = true; - for (int i = 0, len = lenb /*Lemma.usedCharsInPattern(pattern)*/; i < len; i++) { - int val = (int) ((pattern >>> (i * 5)) & 31); - if (val != 0) { - long[] bs = entry.posBitsets[i * 26 + (val - 1)]; + for (long p = pattern; p != 0; ) { + int combined = (int) (p & 0xFF); + if (combined != 0) { + long[] bs = entry.posBitsets[combined - 1]; if (first) { System.arraycopy(bs, 0, res, 0, numLongs); first = false; } else { for (int k = 0; k < numLongs; k++) res[k] &= bs[k]; } + p >>>= 8; + } else { + p >>>= (Long.numberOfTrailingZeros(p) & ~7); } } @@ -803,7 +829,7 @@ public record SwedishGenerator(Rng rng) { return count; } //72 << 3; - static final int BIGG = 581 + 1; + static final int BIGG = (288 | 3) + 1; public FillResult fillMask(Grid mask, DictEntry[] dictIndex, int timeLimitMs) { val multiThreaded = Thread.currentThread().getName().contains("pool"); @@ -812,8 +838,7 @@ public record SwedishGenerator(Rng rng) { long[] assigned = new long[BIGG]; val ctx = CTX.get(); - val count = ctx.cellCount; - Arrays.fill(count, 0, SIZE, 0); + val count = new int[SIZE]; val slots = extractSlots(grid); for (var s : slots) { @@ -912,7 +937,7 @@ public record SwedishGenerator(Rng rng) { for (var t = 0; t < tries; t++) { double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * L); + int idxInArray = (int) (r * r * r * (L - 1)); var idx = idxs[idxInArray]; var w = entry.words[idx]; var lemIdx = Lemma.unpackIndex(w); @@ -942,7 +967,7 @@ public record SwedishGenerator(Rng rng) { var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * N); + int idxInArray = (int) (r * r * r * (N - 1)); var w = entry.words[idxInArray]; var lemIdx = Lemma.unpackIndex(w); if (used.get(lemIdx)) continue; @@ -975,20 +1000,14 @@ public record SwedishGenerator(Rng rng) { } stats.seconds = (System.currentTimeMillis() - t0) / 1000.0; - Map lemmaMap = new HashMap<>(); - for (var i = 0; i < assigned.length; i++) { - if (assigned[i] != X) { - lemmaMap.put(i, assigned[i]); - } - } - var res = new FillResult(ok, new Gridded(grid), lemmaMap, stats); + var res = new FillResult(ok, new Gridded(grid), assigned, 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", - lemmaMap.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds + res.wordCount(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds ) ); } diff --git a/src/test/java/puzzle/ExportFormatTest.java b/src/test/java/puzzle/ExportFormatTest.java index 17cbc0d..4e0cdbc 100644 --- a/src/test/java/puzzle/ExportFormatTest.java +++ b/src/test/java/puzzle/ExportFormatTest.java @@ -19,11 +19,11 @@ import static puzzle.SwedishGenerator.*; public class ExportFormatTest { - static final byte CLUE_DOWN = 0; + static final byte CLUE_DOWN = 0; static final byte CLUE_RIGHT = 1; - static final byte CLUE_UP = 2; - static final byte CLUE_LEFT = 3; - + static final byte CLUE_UP = 2; + static final byte CLUE_LEFT = 3; + @Test void testExportFormatFromFilled() { var swe = new SwedishGenerator(new Rng(0)); @@ -33,10 +33,10 @@ public class ExportFormatTest { grid.setClue(0, CLUE_RIGHT); // This creates a slot starting at (0,1) - var clueMap = new HashMap(); + var clueMap = new long[300]; // key = (cellIndex << 2) | (direction) - var key = (0 << 2) | (CLUE_RIGHT); - clueMap.put(key, Lemma.from("TEST")); + var key = (0 << 2) | (CLUE_RIGHT); + clueMap[key] = Lemma.from("TEST"); // Manually fill the grid letters for "TEST" at (0,1), (0,2), (0,3), (0,4) grid.setLetter(Grid.offset(0, 1), (byte) 'T'); @@ -86,7 +86,7 @@ public class ExportFormatTest { void testExportFormatEmpty() { var swe = new SwedishGenerator(new Rng(0)); var grid = Grid.createEmpty(); - var fillResult = new FillResult(true, new Gridded(grid), new HashMap<>(), new FillStats()); + var fillResult = new FillResult(true, new Gridded(grid), new long[300], new FillStats()); var puzzleResult = new PuzzleResult(swe, null, null, fillResult); var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0)); diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index e03b5af..0603991 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -16,21 +16,21 @@ import static puzzle.SwedishGenerator.DASH; public class MainTest { - static final byte LETTER_A = (byte) 'A'; - static final byte LETTER_B = (byte) 'B'; - static final byte LETTER_Z = (byte) 'Z'; - static final byte CLUE_DOWN = 0; + static final byte LETTER_A = (byte) 'A'; + static final byte LETTER_B = (byte) 'B'; + static final byte LETTER_Z = (byte) 'Z'; + static final byte CLUE_DOWN = 0; static final byte CLUE_RIGHT = 1; - static final byte CLUE_UP = 2; - static final byte CLUE_LEFT = 3; - + static final byte CLUE_UP = 2; + static final byte CLUE_LEFT = 3; + static final int OFF_0_0 = Grid.offset(0, 0); static final int OFF_0_1 = Grid.offset(0, 1); static final int OFF_0_2 = Grid.offset(0, 2); static final int OFF_1_1 = Grid.offset(1, 1); static final int OFF_1_2 = Grid.offset(1, 2); static final int OFF_2_3 = Grid.offset(2, 3); - + @Test void testExtractSlots() { var grid = Grid.createEmpty(); @@ -163,7 +163,7 @@ public class MainTest { foundSeed = seed; System.out.println("[DEBUG_LOG] Seed found: " + seed); System.out.println("[DEBUG_LOG] Simplicity: " + res.filled().stats().simplicity); - System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().clueMap().size()); + System.out.println("[DEBUG_LOG] ClueMap Size: " + res.filled().wordCount()); System.out.println("[DEBUG_LOG] Grid:"); System.out.println(res.filled().grid().renderHuman()); var aa = res.exportFormatFromFilled(1, new Rewards(1, 1, 1)); @@ -177,8 +177,8 @@ public class MainTest { // Regression baseline for seed search starting at 12347, pop 4, gens 20 Assertions.assertEquals(12348, foundSeed, "Found seed changed"); - Assertions.assertEquals(18, res.filled().clueMap().size(), "Number of assigned words changed"); - Assertions.assertEquals("TROTS", Lemma.asWord(res.filled().clueMap().get(282))); + Assertions.assertEquals(18, res.filled().wordCount(), "Number of assigned words changed"); + Assertions.assertEquals("SLEDE", Lemma.asWord(res.filled().clueMap()[282])); Assertions.assertEquals(74732156493031040L, res.filled().grid().grid().lo); Assertions.assertEquals(193L, res.filled().grid().grid().hi); } diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index bcf0a09..3eec1a0 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -39,7 +39,7 @@ public class SwedishGeneratorTest { var slot = Slot.from(18 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); long pattern = patternForSlot(grid, slot); - assertEquals(1 | (2 << 5) | (3 << 10), pattern); + assertEquals(1L | (28L << 8) | (55L << 16), pattern); } @Test @@ -48,7 +48,7 @@ public class SwedishGeneratorTest { var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); long pattern = patternForSlot(grid, slot); - assertEquals(1 | (0 << 5) | (3 << 10), pattern); + assertEquals(1L | (0L << 8) | (55L << 16), pattern); } @Test @@ -57,7 +57,7 @@ public class SwedishGeneratorTest { var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); long pattern = patternForSlot(grid, slot); - assertEquals(0, pattern); + assertEquals(0L, pattern); } @Test @@ -66,7 +66,7 @@ public class SwedishGeneratorTest { var slot = Slot.from(1 << Slot.BIT_FOR_DIR | (CLUE_RIGHT), 7L, 0L); long pattern = patternForSlot(grid, slot); - assertEquals(1, pattern); + assertEquals(1L, pattern); } @Test void testRng() { @@ -232,6 +232,18 @@ public class SwedishGeneratorTest { assertEquals(0, count); } + static long packPattern(String s) { + long p = 0; + byte[] b = s.getBytes(StandardCharsets.US_ASCII); + for (int i = 0; i < b.length; i++) { + int val = b[i] & 31; + if (val != 0) { + p |= (long) (i * 26 + val) << (i << 3); + } + } + return p; + } + @Test void testCandidateInfoForPattern() { var l0 = Lemma.from("IN"); @@ -247,7 +259,7 @@ public class SwedishGeneratorTest { // Pattern "APP--" for length 5 var context = new Context(); - context.setPattern(Lemma.pack(new byte[]{ 'A', 'P', 'P', DASH, DASH })); + context.setPattern(packPattern("APP")); var info = candidateInfoForPattern(context, dict.index()[5], 5); assertEquals(2, info.count()); @@ -410,14 +422,14 @@ public class SwedishGeneratorTest { var entry5 = dict.index()[5]; var ctx = new Context(); - ctx.setPattern(Lemma.pack("APP".getBytes(StandardCharsets.US_ASCII))); - assertEquals(2, candidateCountForPattern(ctx, entry5,3)); + ctx.setPattern(packPattern("APP")); + assertEquals(2, candidateCountForPattern(ctx, entry5, 3)); - ctx.setPattern(Lemma.pack("BAN".getBytes(StandardCharsets.US_ASCII))); - assertEquals(1, candidateCountForPattern(ctx, entry5,3)); + ctx.setPattern(packPattern("BAN")); + assertEquals(1, candidateCountForPattern(ctx, entry5, 3)); - ctx.setPattern(Lemma.pack("CAT".getBytes(StandardCharsets.US_ASCII))); - assertEquals(0, candidateCountForPattern(ctx, entry5,3)); + ctx.setPattern(packPattern("CAT")); + assertEquals(0, candidateCountForPattern(ctx, entry5, 3)); } @Test