From 81ae0aa84c02d5ac740f060c6773d571a4b26e20 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 13 Jan 2026 01:17:08 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/Export.java | 21 ---- src/main/java/puzzle/SwedishGenerator.java | 108 +++++++++--------- .../java/puzzle/SwedishGeneratorTest.java | 6 +- 3 files changed, 59 insertions(+), 76 deletions(-) diff --git a/src/main/java/puzzle/Export.java b/src/main/java/puzzle/Export.java index 2a67b56..245fbdd 100644 --- a/src/main/java/puzzle/Export.java +++ b/src/main/java/puzzle/Export.java @@ -127,27 +127,6 @@ public record Export() { } } - static class Bit { - - long l1, l2; - public boolean get(int bitIndex) { - if ((bitIndex & 64) == 0) return (l1 & (1L << bitIndex)) != 0L; - return (l2 & (1L << (bitIndex & 63))) != 0L; - } - public void set(int bitIndex) { - if ((bitIndex & 64) == 0) this.l1 |= 1L << bitIndex; - else this.l2 |= 1L << (bitIndex & 63); - } - public void or(long lo, long hi) { - this.l1 |= lo; - this.l2 |= hi; - } - public void clear() { - l1 = 0L; - l2 = 0L; - } - } - record Bit1029(long[] bits) { public Bit1029() { this(new long[2048]); } diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a8bac21..3347dfe 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -4,7 +4,6 @@ import lombok.Getter; import lombok.val; import precomp.Neighbors9x8; import precomp.Neighbors9x8.rci; -import puzzle.Export.Bit; import puzzle.Export.Bit1029; import puzzle.Export.DictEntryDTO; import puzzle.Export.Gridded; @@ -53,7 +52,6 @@ public record SwedishGenerator(Rng rng) { static final int MAX_WORD_LENGTH = C <= R ? C : R; static final int MAX_WORD_LENGTH_PLUS_ONE = MAX_WORD_LENGTH + 1; static final int MIN_LEN = Config.MIN_LEN; - static final int MIN_LEN7 = Config.MIN_LEN * 7; 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; @@ -66,15 +64,15 @@ public record SwedishGenerator(Rng rng) { // 0b01 // 0b10 - static final byte B0 = (byte) 0; - static final byte B64 = (byte) 64; - static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE; - static final rci[] IT = Neighbors9x8.IT; - static final int[][] MUTATE_RI = new int[SIZE][625]; - static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; - static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; - static final long[] PATH_LO = Neighbors9x8.PATH_LO; - static final long[] PATH_HI = Neighbors9x8.PATH_HI; + static final byte B0 = (byte) 0; + static final byte B64 = (byte) 64; + static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSET_D_IDX_0_BASE; + static final rci[] IT = Neighbors9x8.IT; + static final int[][] MUTATE_RI = new int[SIZE][625]; + static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO; + static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI; + static final long[] PATH_LO = Neighbors9x8.PATH_LO; + static final long[] PATH_HI = Neighbors9x8.PATH_HI; static { for (int i = 0; i < SIZE; i++) { @@ -116,11 +114,8 @@ public record SwedishGenerator(Rng rng) { static final class Context { - final Bit covH2 = new Bit(); - final Bit covV2 = new Bit(); final int[] cellCount = new int[SIZE]; final int[] stack = new int[SIZE]; - final Bit seen = new Bit(); long pattern; final long[] undo = new long[128]; final long[] bitset = new long[2500]; @@ -149,7 +144,8 @@ public record SwedishGenerator(Rng rng) { var range = (long) max - (long) min + 1L; return (byte) (min + (u % range)); } - int randint2bit() { return nextU32() & 3; } + int randint2bit() { return nextU32() & 3; } + byte randint2bitByte() { return (byte) (nextU32() & 3); } int randint(int min, int max) { var u = (nextU32() & 0xFFFFFFFFL); var range = (long) max - (long) min + 1L; @@ -227,7 +223,7 @@ public record SwedishGenerator(Rng rng) { } } - static record DictEntry(long[] words, long[][] posBitsets) { } + static record DictEntry(long[] words, long[][] posBitsets, int numlong) { } static int LEMMA_COUNTER = 0; @@ -240,7 +236,7 @@ public record SwedishGenerator(Rng rng) { static long pack(int index, byte[] b) { return pack(b) | ((long) index << 40); } static long pack(byte[] b) { long w = 0; - for (var i = 0; i < b.length; i++) w |= ((long) b[i] & ~64) << (i * 5); + for (var i = 0; i < b.length; i++) w |= ((long) b[i] & 63) << (i * 5); return w; } static public long from(String word) { return pack(LEMMA_COUNTER++, word.getBytes(US_ASCII)); } @@ -295,7 +291,7 @@ public record SwedishGenerator(Rng rng) { } } } - return new DictEntry(words, bitsets); + return new DictEntry(words, bitsets, (words.length + 63) >>> 6); }).toArray(DictEntry[]::new), Arrays.stream(index).mapToInt(i -> i.words().size()).sum()); } @@ -330,7 +326,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 = (idx << 2) | d; + int key = Slot.packSlotDir(idx, d); long rayLo = PATH_LO[key]; long rayHi = PATH_HI[key]; @@ -341,9 +337,8 @@ public record SwedishGenerator(Rng rng) { // slice ray to stop before first clue, depending on direction monotonicity // right/down => increasing indices; up/left => decreasing indices - boolean increasing = Slot.increasing(d); - if (increasing) { + if (Slot.increasing(d)) { // first clue is lowest index among hits (lo first, then hi) if (hitsLo != 0) { long stop = 1L << Long.numberOfTrailingZeros(hitsLo); @@ -369,7 +364,7 @@ public record SwedishGenerator(Rng rng) { } if ((rayLo | rayHi) != 0) { - visitor.visit(Slot.packSlotDir(idx, d), rayLo, rayHi); + visitor.visit(key, rayLo, rayHi); } } @@ -380,11 +375,8 @@ public record SwedishGenerator(Rng rng) { } long maskFitness(Grid grid) { - var ctx = CTX.get(); - var covH = ctx.covH2; - var covV = ctx.covV2; - covH.clear(); - covV.clear(); + 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); boolean hasSlots = false; @@ -413,8 +405,13 @@ public record SwedishGenerator(Rng rng) { } if ((rLo | rHi) != 0) { hasSlots = true; - if (Slot.horiz(d)) covH.or(rLo, rHi); - else covV.or(rLo, rHi); + if (Slot.horiz(d)) { + cHLo |= rLo; + cHHi |= rHi; + } else { + cVLo |= rLo; + cVHi |= rHi; + } if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000; } else { penalty += 25000; @@ -485,18 +482,25 @@ 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) { - 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.get(clueIdx); - var v = covV.get(clueIdx); - if (!h && !v) penalty += 1500; - else if (h && v) { /* ok */ } else if (h | v) penalty += 200; - else penalty += 600; - } + for (long bits = ~lo_cl; bits != X; bits &= bits - 1) { + int clueIdx = 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; + boolean h = (cHLo & (1L << clueIdx)) != 0; + boolean v = (cVLo & (1L << clueIdx)) != 0; + if (!h && !v) penalty += 1500; + else if (h && v) { /* ok */ } else if (h | v) penalty += 200; + else penalty += 600; + } + for (long bits = ~hi_cl & 0xFFL; bits != X; bits &= bits - 1) { + int clueIdx = Long.numberOfTrailingZeros(bits); + var rci = IT[64 | clueIdx]; + if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; + boolean h = (cHHi & (1L << clueIdx)) != 0; + boolean v = (cVHi & (1L << clueIdx)) != 0; + if (!h && !v) penalty += 1500; + else if (h && v) { /* ok */ } else if (h | v) penalty += 200; + else penalty += 600; } return penalty; @@ -507,9 +511,9 @@ public record SwedishGenerator(Rng rng) { for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { idx = rng.randint(0, SIZE_MIN_1); if (g.isClue(idx)) continue; - int d_idx = rng.randint2bit(); + var d_idx = rng.randint2bitByte(); if (g.hasRoomForClue(OFFSETS_D_IDX[d_idx | idx << 2])) { - g.setClue(idx, (byte) d_idx); + g.setClue(idx, d_idx); placed++; } } @@ -522,9 +526,9 @@ public record SwedishGenerator(Rng rng) { for (var k = 0; k < 4; k++) { ri = bytes[rng.randint(0, 624)]; if (!g.clueless(ri)) { - int d_idx = rng.randint2bit(); + var d_idx = rng.randint2bitByte(); val packed = OFFSETS_D_IDX[d_idx | ri << 2]; - if (g.hasRoomForClue(packed)) g.setClue(ri, (byte) d_idx); + if (g.hasRoomForClue(packed)) g.setClue(ri, d_idx); } } return g; @@ -555,7 +559,7 @@ public record SwedishGenerator(Rng rng) { out.lo = bo0; out.hi = bo1; for (var lo = out.lo; lo != X; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.hi; hi != X; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); + 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); } @@ -735,11 +739,11 @@ public record SwedishGenerator(Rng rng) { return new CandidateInfo(null, entry.words.length); } - int numLongs = (entry.words.length + 63) >>> 6; + int numLongs = entry.numlong; long[] res = ctx.bitset; boolean first = true; - for (int i = 0, len = Lemma.usedCharsInPattern(pattern); i < len; i++) { + 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)]; @@ -771,15 +775,15 @@ public record SwedishGenerator(Rng rng) { return new CandidateInfo(indices, count); } - static int candidateCountForPattern(Context ctx, DictEntry entry) { + static int candidateCountForPattern(Context ctx, DictEntry entry, int lenb) { long pattern = ctx.pattern; if (pattern == X) return entry.words.length; - int numLongs = (entry.words.length + 63) >>> 6; + int numLongs = entry.numlong; long[] res = ctx.bitset; boolean first = true; - for (int i = 0, len = Lemma.usedCharsInPattern(pattern); i < len; i++) { + 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)]; @@ -859,7 +863,7 @@ public record SwedishGenerator(Rng rng) { var entry = dictIndex[s.len()]; if (entry == null) return PICK_NOT_DONE; ctx.pattern = patternForSlot(grid, s); - int count = candidateCountForPattern(ctx, entry); + int count = candidateCountForPattern(ctx, entry, s.len()); if (count == 0) return PICK_NOT_DONE; if (best == null diff --git a/src/test/java/puzzle/SwedishGeneratorTest.java b/src/test/java/puzzle/SwedishGeneratorTest.java index 368c406..bcf0a09 100644 --- a/src/test/java/puzzle/SwedishGeneratorTest.java +++ b/src/test/java/puzzle/SwedishGeneratorTest.java @@ -411,13 +411,13 @@ public class SwedishGeneratorTest { var ctx = new Context(); ctx.setPattern(Lemma.pack("APP".getBytes(StandardCharsets.US_ASCII))); - assertEquals(2, candidateCountForPattern(ctx, entry5)); + assertEquals(2, candidateCountForPattern(ctx, entry5,3)); ctx.setPattern(Lemma.pack("BAN".getBytes(StandardCharsets.US_ASCII))); - assertEquals(1, candidateCountForPattern(ctx, entry5)); + assertEquals(1, candidateCountForPattern(ctx, entry5,3)); ctx.setPattern(Lemma.pack("CAT".getBytes(StandardCharsets.US_ASCII))); - assertEquals(0, candidateCountForPattern(ctx, entry5)); + assertEquals(0, candidateCountForPattern(ctx, entry5,3)); } @Test