From 2430dbdfb9efd68073e3216dff834f94ec24bcf4 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 13 Jan 2026 23:29:32 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 114 ++++++++---------- src/test/java/puzzle/MainTest.java | 2 +- .../java/puzzle/SwedishGeneratorTest.java | 22 ++-- 3 files changed, 62 insertions(+), 76 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 74b3410..a44b222 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -14,12 +14,20 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; 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.*; +/** + * NOTE: + * A) generate randoms for and idx and direction (clue together) + * B) convert IDX_PATH_0_BASE into bit-pattern (i think) Length should not be checked, just left empty if not good. + * C) store more information in the byte[] even consider the long[] or try sqeeze the 288 options (clue) in a byte. + * D) separate clue into three long pairs of hi and lo, (v,h,r) so we do not store the clue anymore in the grid array at all + * E) store letter-set also in a long pair so we can use it in path determinations + * F) pre-determine random clue arrangements, so they do not involve impossible clue's such as bottom at last or the line before that and such to all sides + */ + /** * SwedishGenerator.java * @@ -53,7 +61,6 @@ public record SwedishGenerator(Rng rng) { static final int MAX_TRIES_PER_SLOT = Config.MAX_TRIES_PER_SLOT; static final char C_DASH = '\0'; static final byte _1 = 49, _9 = 57, A = 65, Z = 90, DASH = (byte) C_DASH; - static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); } record Pick(Slot slot, CandidateInfo info, boolean done) { } @@ -124,14 +131,12 @@ public record SwedishGenerator(Rng rng) { } } - static final class Context { + static final record Context(int[] stack, long[] undo, long[] bitset) { - final int[] stack = new int[SIZE]; - long pattern; - final long[] undo = new long[128]; - final long[] bitset = new long[2500]; + public Context() { this(new int[SIZE], new long[128], new long[2500]); } + private static final ThreadLocal CTX = ThreadLocal.withInitial(Context::new); - void setPattern(long p) { this.pattern = p; } + public static Context get() { return CTX.get(); } } static final class Rng { @@ -163,6 +168,10 @@ public record SwedishGenerator(Rng rng) { return (int) (min + (u % range)); } double nextFloat() { return (nextU32() & 0xFFFFFFFFL) / 4294967295.0; } + int biasedIndexPow3(int N) { + int m = Math.min(nextU32(), Math.min(nextU32(), nextU32())); + return (int) (((m & 0xFFFFFFFFL) * (long) N) >>> 32); + } } static class Grid { @@ -223,7 +232,7 @@ public record SwedishGenerator(Rng rng) { return same / SIZED; } int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } - boolean hasRoomForClue(long packed) { return (packed & GT_1_OFFSET_53_BIT) != X && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } + boolean hasRoomForClue(long packed) { return (packed) != 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)); @@ -234,7 +243,7 @@ public record SwedishGenerator(Rng rng) { } } - static record DictEntry(long[] words, long[][] posBitsets, int numlong) { } + static record DictEntry(long[] words, long[][] posBitsets, CandidateInfo empty, int length, int numlong) { } static int LEMMA_COUNTER = 0; @@ -301,7 +310,7 @@ public record SwedishGenerator(Rng rng) { } } } - return new DictEntry(words, bitsets, (words.length + 63) >>> 6); + return new DictEntry(words, bitsets, new CandidateInfo(null, words.length), words.length, (words.length + 63) >>> 6); }).toArray(DictEntry[]::new), Arrays.stream(index).mapToInt(i -> i.words().size()).sum()); } @@ -322,7 +331,7 @@ public record SwedishGenerator(Rng rng) { static final int BIT_FOR_DIR = 2; static Slot from(int key, long lo, long hi) { return new Slot(key, lo, hi); } - public int len() { return Long.bitCount(lo) + Long.bitCount(hi); } + public int length() { return Long.bitCount(lo) + Long.bitCount(hi); } public int clueIndex() { return clueIndex(key); } public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; } public static int dir(int key) { return key & 3; } @@ -411,7 +420,7 @@ public record SwedishGenerator(Rng rng) { rLo &= ~((1L << msb << 1) - 1); } } - if ((rLo | rHi) != 0) { + if ((rLo | rHi) != 0L) { hasSlots = true; if (Slot.horiz(key)) { cHLo |= rLo; @@ -428,7 +437,7 @@ public record SwedishGenerator(Rng rng) { } if (!hasSlots) return 1_000_000_000L; - var ctx = CTX.get(); + var ctx = Context.get(); var stack = ctx.stack; long seenLo = 0L, seenHi = 0L; @@ -535,8 +544,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[Slot.packSlotDir(ri, d_idx)]; - if (g.hasRoomForClue(packed)) g.setClue(ri, d_idx); + if (g.hasRoomForClue(OFFSETS_D_IDX[Slot.packSlotDir(ri, d_idx)])) g.setClue(ri, d_idx); } } return g; @@ -692,7 +700,7 @@ public record SwedishGenerator(Rng rng) { int cross = 0; for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1); for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1); - return cross * 10 + s.len(); + return cross * 10 + s.length(); } static boolean placeWord(Grid grid, Slot s, long w, long[] undoBuffer, int offset) { long maskLo = 0, maskHi = 0; @@ -753,12 +761,7 @@ public record SwedishGenerator(Rng rng) { return true; } - static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int lenb) { - var pattern = ctx.pattern; - if (pattern == X) { - return new CandidateInfo(null, entry.words.length); - } - + static CandidateInfo candidateInfoForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { int numLongs = entry.numlong; long[] res = ctx.bitset; boolean first = true; @@ -782,8 +785,6 @@ public record SwedishGenerator(Rng rng) { int count = 0; for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]); - if (count == 0) return new CandidateInfo(null, 0); - int[] indices = new int[count]; int ki = 0; for (int k = 0; k < numLongs; k++) { @@ -798,10 +799,7 @@ public record SwedishGenerator(Rng rng) { return new CandidateInfo(indices, count); } - static int candidateCountForPattern(Context ctx, DictEntry entry, int lenb) { - long pattern = ctx.pattern; - if (pattern == X) return entry.words.length; - + static int candidateCountForPattern(Context ctx, long pattern, DictEntry entry, int lenb) { int numLongs = entry.numlong; long[] res = ctx.bitset; boolean first = true; @@ -822,8 +820,6 @@ public record SwedishGenerator(Rng rng) { } } - if (first) return entry.words.length; // should not happen if pattern != X - int count = 0; for (int k = 0; k < numLongs; k++) count += Long.bitCount(res[k]); return count; @@ -837,7 +833,7 @@ public record SwedishGenerator(Rng rng) { val used = new Bit1029(); long[] assigned = new long[BIGG]; - val ctx = CTX.get(); + val ctx = Context.get(); val count = new int[SIZE]; val slots = extractSlots(grid); @@ -879,35 +875,30 @@ public record SwedishGenerator(Rng rng) { System.out.flush(); } Pick chooseMRV() { - Slot best = null; - CandidateInfo bestInfo = null; - int bestScore = -1; - for (int i = 0, n = slots.size(); i < n; i++) { + Slot best = null; + for (int i = 0, count, count2 = -1, bestScore = -1, n = slots.size(); i < n; i++) { var s = slots.get(i); - if (assigned[s.key()] != X) continue; - var entry = dictIndex[s.len()]; - if (entry == null) return PICK_NOT_DONE; - ctx.pattern = patternForSlot(grid, s); - int count = candidateCountForPattern(ctx, entry, s.len()); + if (assigned[s.key] != X) continue; + var pattern = patternForSlot(grid, s); + var index = dictIndex[s.length()]; + count = pattern == X ? index.length : candidateCountForPattern(ctx, pattern, index, s.length()); if (count == 0) return PICK_NOT_DONE; if (best == null - || count < bestInfo.count - || (count == bestInfo.count && slotScores[i] > bestScore)) { + || count < count2 + || (count == count2 && slotScores[i] > bestScore)) { best = s; bestScore = slotScores[i]; - bestInfo = new CandidateInfo(null, count); + count2 = count; if (count <= 1) break; } } - - if (best == null) return PICK_DONE; - // Re-calculate for the best slot to get actual indices - ctx.pattern = patternForSlot(grid, best); - bestInfo = candidateInfoForPattern(ctx, dictIndex[best.len()], best.len()); - - return new Pick(best, bestInfo, false); + if (best == null) return PICK_DONE; + var pattern = patternForSlot(grid, best); + var index = dictIndex[best.length()]; + if (pattern == X) return new Pick(best, index.empty, false); + return new Pick(best, candidateInfoForPattern(ctx, pattern, index, best.length()), false); } boolean backtrack(int depth) { if (Thread.currentThread().isInterrupted()) return false; @@ -926,8 +917,8 @@ public record SwedishGenerator(Rng rng) { renderProgress(); var s = pick.slot; - var k = s.key(); - int patLen = s.len(); + var k = s.key; + int patLen = s.length(); var entry = dictIndex[patLen]; if (info.indices != null && info.indices.length > 0) { @@ -936,11 +927,12 @@ public record SwedishGenerator(Rng rng) { var tries = Math.min(MAX_TRIES_PER_SLOT, L); for (var t = 0; t < tries; t++) { - double r = rng.nextFloat(); - int idxInArray = (int) (r * r * r * (L - 1)); - var idx = idxs[idxInArray]; - var w = entry.words[idx]; - var lemIdx = Lemma.unpackIndex(w); + var r = rng.nextFloat(); + //int idxInArray = rng.biasedIndexPow3(L - 1); + int idxInArray = (int) (r * r * r * (L - 1)); + var idx = idxs[idxInArray]; + var w = entry.words[idx]; + var lemIdx = Lemma.unpackIndex(w); if (used.get(lemIdx)) continue; if (!placeWord(grid, s, w, ctx.undo, depth)) continue; @@ -959,10 +951,6 @@ public record SwedishGenerator(Rng rng) { } var N = entry.words.length; - if (N == 0) { - stats.backtracks++; - return false; - } var tries = Math.min(MAX_TRIES_PER_SLOT, N); for (var t = 0; t < tries; t++) { diff --git a/src/test/java/puzzle/MainTest.java b/src/test/java/puzzle/MainTest.java index 0603991..8df76d2 100644 --- a/src/test/java/puzzle/MainTest.java +++ b/src/test/java/puzzle/MainTest.java @@ -44,7 +44,7 @@ public class MainTest { var slots = extractSlots(grid); assertEquals(1, slots.size()); var s = slots.getFirst(); - assertEquals(8, s.len()); + assertEquals(8, s.length()); var cells = s.walk().toArray(); assertEquals(0, Grid.r(cells[0])); assertEquals(1, Grid.c(cells[0])); diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 3eec1a0..9fc22d9 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -233,7 +233,7 @@ public class SwedishGeneratorTest { } static long packPattern(String s) { - long p = 0; + long p = 0; byte[] b = s.getBytes(StandardCharsets.US_ASCII); for (int i = 0; i < b.length; i++) { int val = b[i] & 31; @@ -258,9 +258,7 @@ public class SwedishGeneratorTest { var dict = new Dict(new long[]{ l0, l1, l2, l3, l3a, l4a, l6a, l7a, l8a }); // Pattern "APP--" for length 5 - var context = new Context(); - context.setPattern(packPattern("APP")); - var info = candidateInfoForPattern(context, dict.index()[5], 5); + var info = candidateInfoForPattern(Context.get(), packPattern("APP"), dict.index()[5], 5); assertEquals(2, info.count()); assertNotNull(info.indices()); @@ -279,7 +277,7 @@ public class SwedishGeneratorTest { assertEquals(1, slots.size()); var s = slots.getFirst(); - assertTrue(s.len() >= 2); + assertTrue(s.length() >= 2); assertEquals(OFF_0_0, s.clueIndex()); assertEquals(CLUE_RIGHT, Slot.dir(s.key())); } @@ -421,15 +419,15 @@ public class SwedishGeneratorTest { var dict = new Dict(words); var entry5 = dict.index()[5]; - var ctx = new Context(); - ctx.setPattern(packPattern("APP")); - assertEquals(2, candidateCountForPattern(ctx, entry5, 3)); + var ctx = Context.get(); + long pattern = packPattern("APP"); + assertEquals(2, candidateCountForPattern(ctx, pattern, entry5, 3)); - ctx.setPattern(packPattern("BAN")); - assertEquals(1, candidateCountForPattern(ctx, entry5, 3)); + pattern = packPattern("BAN"); + assertEquals(1, candidateCountForPattern(ctx, pattern, entry5, 3)); - ctx.setPattern(packPattern("CAT")); - assertEquals(0, candidateCountForPattern(ctx, entry5, 3)); + pattern = packPattern("CAT"); + assertEquals(0, candidateCountForPattern(ctx, pattern, entry5, 3)); } @Test