From 986c2f85a9ede9e5474a02e2ee29a9b273fed132 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 11 Jan 2026 23:11:16 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 2 +- src/main/java/puzzle/Main.java | 2 +- src/main/java/puzzle/SwedishGenerator.java | 69 +++++++++++-------- src/test/java/puzzle/MainTest.java | 2 +- .../java/puzzle/SwedishGeneratorTest.java | 10 +-- 5 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 97b9dc2..9f32996 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -93,7 +93,7 @@ public record Export() { public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) { public WordOut(Lemma l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) { - this(new String(l.word(), US_ASCII), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, l.simpel(), l.clue()); + this(l.asWord(), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed, l.simpel(), l.clue()); } } diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 46717bb..808ec2c 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -347,7 +347,7 @@ public class Main { static PuzzleResult attempt(Rng rng, Dict dict, Opts opts) { TOTAL_ATTEMPTS.incrementAndGet(); var swe = new SwedishGenerator(rng); - var mask = swe.generateMask(opts.pop, opts.gens); + var mask = swe.generateMask(opts.pop, opts.gens, Math.max(opts.pop, (int) Math.floor(opts.pop * 1.5))); var filled = swe.fillMask(mask, dict.index(), opts.fillTimeout); TOTAL_NODES.addAndGet(filled.stats().nodes); diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a9cb7b2..0bc6b46 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -19,6 +19,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; +import java.util.stream.IntStream; +import static java.nio.charset.StandardCharsets.*; /** * SwedishGenerator.java @@ -30,7 +32,10 @@ import java.util.Locale; @SuppressWarnings("ALL") public record SwedishGenerator(Rng rng) { - record CandidateInfo(int[] indices, int count) { } + record CandidateInfo(int[] indices, int count) { + + public CandidateInfo(int n) { this(null, n); } + } //@formatter:off @FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } @@ -283,15 +288,25 @@ public record SwedishGenerator(Rng rng) { } } - public static record Lemma(int index, byte[] word, int simpel) { + public static record Lemma(int index, long word, byte len, short simpel) { static int LEMMA_COUNTER = 0; - public Lemma(int index, String word, int simpel) { this(index, word.getBytes(StandardCharsets.US_ASCII), simpel); } + static long pack(byte[] b) { + long w = 0; + for (var i = 0; i < b.length; i++) w |= ((long) b[i] & ~64) << (i * 5); + return w; + } + public Lemma(int index, String word, int simpel) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length(), (short) simpel); } public Lemma(String word, int simpel) { this(LEMMA_COUNTER++, word, simpel); } - byte byteAt(int idx) { return word[idx]; } + byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | Grid.B64); }// word[]; } @Override public int hashCode() { return index; } @Override public boolean equals(Object o) { return (o == this) || (o instanceof Lemma l && l.index == index); } String[] clue() { return CsvIndexService.clues(index); } + public String asWord() { + var b = new byte[len]; + for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | Grid.B64); + return new String(b, US_ASCII); + } } public static record Dict( @@ -302,7 +317,7 @@ public record SwedishGenerator(Rng rng) { var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE]; Arrays.setAll(index, i -> new DictEntry(i)); for (var lemma : wordz) { - var L = lemma.word.length; + var L = lemma.len; var entry = index[L]; var idx = entry.words.size(); @@ -320,7 +335,7 @@ public record SwedishGenerator(Rng rng) { static Dict loadDict(String wordsPath) { try { var map = new ArrayList(); - Files.lines(Path.of(wordsPath), StandardCharsets.UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add)); + Files.lines(Path.of(wordsPath), UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add)); return new Dict(map.toArray(Lemma[]::new)); } catch (IOException e) { e.printStackTrace(); @@ -549,7 +564,7 @@ public record SwedishGenerator(Rng rng) { return best; } - public Grid generateMask(int popSize, int gens) { + public Grid generateMask(int popSize, int gens, int pairs) { class GridAndFit { Grid grid; @@ -569,7 +584,6 @@ public record SwedishGenerator(Rng rng) { for (var gen = 0; gen < gens; gen++) { if (Thread.currentThread().isInterrupted()) break; var children = new ArrayList(); - var pairs = Math.max(popSize, (int) Math.floor(popSize * 1.5)); for (var k = 0; k < pairs; k++) { var p1 = pop.get(rng.randint(0, pop.size() - 1)); @@ -595,18 +609,16 @@ public record SwedishGenerator(Rng rng) { } pop = next; - if (Main.VERBOSE && gen % 10 == 0) { - var bestF = pop.get(0).fit(); - System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + bestF); - } + 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; } static void patternForSlot(Grid grid, Slot s, byte[] pat) { + byte ch; for (int i = 0, len = s.len(); i < len; i++) { - var ch = grid.byteAt(s.pos(i)); + ch = grid.byteAt(s.pos(i)); pat[i] = isLetter(ch) ? ch : DASH; } } @@ -638,26 +650,29 @@ public record SwedishGenerator(Rng rng) { undoBuffer[offset] = mask; 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) { - var pattern = ctx.pattern; - var listBuffer = ctx.intListBuffer; - var listCount = 0; + var pattern = ctx.pattern; + var listBuffer = ctx.intListBuffer; + var listCount = 0; + IntList tmp; + byte ch; for (var i = 0; i < len; i++) { - var ch = pattern[i]; + ch = pattern[i]; if (isLetter(ch)) { listBuffer[listCount++] = entry.pos[i][ch - 'A']; } } if (listCount == 0) { - return new CandidateInfo(null, entry.words.size()); + return CANDIDATES[entry.words.size()]; } // Sort constraints by size to optimize intersection - for (int i = 0; i < listCount - 1; i++) { - for (int j = i + 1; j < listCount; j++) { + for (var i = 0; i < listCount - 1; i++) { + for (var j = i + 1; j < listCount; j++) { if (listBuffer[j].size() < listBuffer[i].size()) { - var tmp = listBuffer[i]; + tmp = listBuffer[i]; listBuffer[i] = listBuffer[j]; listBuffer[j] = tmp; } @@ -668,14 +683,14 @@ public record SwedishGenerator(Rng rng) { var curLen = listBuffer[0].size(); if (listCount == 1) return new CandidateInfo(cur, curLen); - int[] b1 = ctx.inter1; - int[] b2 = ctx.inter2; - int[] in = cur; - int[] out = b1; + val b1 = ctx.inter1; + val b2 = ctx.inter2; + var in = cur; + var out = b1; for (var k = 1; k < listCount; k++) { - var nxt = listBuffer[k]; - curLen = intersectSorted(in, curLen, nxt.data(), nxt.size(), out); + tmp = listBuffer[k]; + curLen = intersectSorted(in, curLen, tmp.data(), tmp.size(), out); in = out; out = (out == b1) ? b2 : b1; if (curLen == 0) break; diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 39c45a1..ad01396 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -168,7 +168,7 @@ public class MainTest { Assertions.assertEquals(12348, foundSeed, "Found seed 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.assertArrayEquals(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()); } @Test public void testIsLetterA() { diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index de27159..cd374b2 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -79,7 +79,7 @@ public class SwedishGeneratorTest { assertFalse(grid.isDigitAt(0)); assertTrue(grid.isDigitAt(Grid.offset(0, 1))); assertFalse(grid.isLetterAt(Grid.offset(0, 1))); - assertTrue(grid.isLetterAt( 0)); + assertTrue(grid.isLetterAt(0)); assertFalse(grid.isLetterAt(Grid.offset(0, 1))); var copy = grid.deepCopyGrid(); @@ -110,8 +110,8 @@ public class SwedishGeneratorTest { var l8a = new Lemma("INERENAE", 1); var l1 = new Lemma("APPLE", 5); - Assertions.assertArrayEquals("APPLE".getBytes(StandardCharsets.US_ASCII), l1.word()); - assertEquals(5, l1.word().length); + Assertions.assertEquals(Lemma.pack("APPLE".getBytes(StandardCharsets.US_ASCII)), l1.word()); + assertEquals(5, l1.len()); assertEquals(5, l1.simpel()); assertEquals((byte) 'A', l1.byteAt(0)); @@ -123,7 +123,7 @@ public class SwedishGeneratorTest { var entry3 = dict.index()[3]; assertEquals(1, entry3.words().size()); - Assertions.assertArrayEquals("AXE".getBytes(StandardCharsets.US_ASCII), entry3.words().getFirst().word()); + assertEquals(Lemma.pack("AXE".getBytes(StandardCharsets.US_ASCII)), entry3.words().getFirst().word()); // Check pos indexing // AXE: A at 0, X at 1, E at 2 @@ -279,7 +279,7 @@ public class SwedishGeneratorTest { // 1. Successful placement in empty grid assertTrue(placeWord(grid, s, w1, undoBuffer, 0)); - assertEquals('A', grid.byteAt( 0)); + assertEquals('A', grid.byteAt(0)); assertEquals('B', grid.byteAt(Grid.offset(0, 1))); assertEquals('C', grid.byteAt(Grid.offset(0, 2))); assertEquals(0b111L, undoBuffer[0]);