From 9b9a6e3550c89e39faa55cf18be2fea5a02ab141 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 11 Jan 2026 18:30:19 +0100 Subject: [PATCH] introduce bitloops --- src/main/java/puzzle/SwedishGenerator.java | 179 ++++++++++++--------- 1 file changed, 105 insertions(+), 74 deletions(-) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index 94f20b7..0c4b834 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -20,7 +20,6 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Locale; -import java.util.function.IntConsumer; /** * SwedishGenerator.java @@ -37,6 +36,7 @@ public record SwedishGenerator(Rng rng) { //@formatter:off @FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } //@formatter:on + static final long X = 0L; static final int LOG_EVERY_MS = 200; static final int BAR_LEN = 22; static final int C = Config.PUZZLE_COLS; @@ -44,6 +44,7 @@ public record SwedishGenerator(Rng rng) { static final int R = Config.PUZZLE_ROWS; static final double CROSS_C = (R - 1) / 2.0; static final int SIZE = C * R;// ~18 + static final int SIZE_MIN_1 = SIZE - 1;// ~18 static final double SIZED = (double) SIZE;// ~18 static final int TARGET_CLUES = SIZE >> 2; static final int MAX_WORD_LENGTH = C <= R ? C : R; @@ -63,6 +64,7 @@ public record SwedishGenerator(Rng rng) { // Directions for '1'..'6' static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS; + static final nbrs_16[] OFFSETS_FOUR = Neighbors9x8.OFFSETS_FOUR; static final nbrs_8[] nbrs8 = Neighbors9x8.nbrs8; static final nbrs_8[] nbrs4 = Neighbors9x8.nbrs4; static final rci[] IT = Neighbors9x8.IT; @@ -80,6 +82,28 @@ public record SwedishGenerator(Rng rng) { clamp(Grid.c(i) + dc1 + dc2, 0, C - 1)); } } + /*static { + Rng trng = new Rng(1); + for (int i = 0; i < SIZE; i++) { + int[] neighborhood = new int[625]; + int k = 0; + for (int dr1 = -2; dr1 <= 2; dr1++) + for (int dr2 = -2; dr2 <= 2; dr2++) + for (int dc1 = -2; dc1 <= 2; dc1++) + for (int dc2 = -2; dc2 <= 2; dc2++) + neighborhood[k++] = Grid.offset(clamp(Grid.r(i) + dr1 + dr2, 0, R - 1), + clamp(Grid.c(i) + dc1 + dc2, 0, C - 1)); + for (k = 0; k < 625; k++) { + long packed = 0; + for (int s = 0; s < 4; s++) { + int ri = neighborhood[trng.randint(0, 624)]; + int d = trng.randint(1, 4); + packed |= ((long) ri | ((long) d << 8)) << (s << 4); + } + MUTATE_RI[i][k] = packed; + } + } + }*/ static final Pick PICK_DONE = new Pick(null, null, true); static final Pick PICK_NOT_DONE = new Pick(null, null, false); @@ -145,6 +169,7 @@ public record SwedishGenerator(Rng rng) { var range = (long) max - (long) min + 1L; return (byte) (min + (u % range)); } + int randint2bit() { return nextU32() & 3; } int randint(int min, int max) { var u = (nextU32() & 0xFFFFFFFFL); var range = (long) max - (long) min + 1L; @@ -154,58 +179,68 @@ public record SwedishGenerator(Rng rng) { } static class Grid { + final byte[] g; - long bo0, bo1; + long lo, hi; public Grid(byte[] g) { this(g, 0, 0); } - public Grid(byte[] g, long bo0, long bo1) { this.g = g; this.bo0 = bo0; this.bo1 = bo1; } - static Grid createEmpty() { return new Grid(new byte[SIZE], 0L, 0L); } + public Grid(byte[] g, long lo, long hi) { + this.g = g; + this.lo = lo; + this.hi = hi; + } + static Grid createEmpty() { return new Grid(new byte[SIZE], X, X); } int digitAt(int index) { return g[index] - 48; } public static int r(int offset) { return offset & 7; } public static int c(int offset) { return offset >>> 3; } static int offset(int r, int c) { return r | (c << 3); } - Grid deepCopyGrid() { return new Grid(g.clone(), bo0, bo1); } + Grid deepCopyGrid() { return new Grid(g.clone(), lo, hi); } public byte byteAt(int pos) { return g[pos]; } void setByteAt(int idx, byte ch) { g[idx] = ch; } void setClue(int idx, byte ch) { g[idx] = ch; - if (idx < 64) bo0 |= (1L << idx); - else bo1 |= (1L << (idx & 63)); + if (idx < 64) lo |= (1L << idx); + else hi |= (1L << (idx & 63)); } void clear(int idx) { g[idx] = DASH; } void clearClue(int idx) { g[idx] = DASH; - if (idx < 64) bo0 &= ~(1L << idx); - else bo1 &= ~(1L << (idx & 63)); + if (idx < 64) lo &= ~(1L << idx); + else hi &= ~(1L << (idx & 63)); } static boolean isDigit(byte b) { return (b & 48) == 48; } boolean isDigitAt(int index) { return isDigit(g[index]); } + boolean isClue(long index) { + if (index < 64) return ((lo >> index) & 1L) != X; + return ((hi >> (index & 63)) & 1L) != X; + } boolean isClue(int index) { - if (index < 64) return ((bo0 >> index) & 1L) != 0; - return ((bo1 >> (index & 63)) & 1L) != 0; + if (index < 64) return ((lo >> index) & 1L) != 0; + return ((hi >> (index & 63)) & 1L) != 0; + } + boolean notClue(long index) { + if (index < 64) return ((lo >> index) & 1L) == X; + return ((hi >> (index & 63)) & 1L) == X; } boolean notClue(int index) { - if (index < 64) return ((bo0 >> index) & 1L) == 0L; - return ((bo1 >> (index & 63)) & 1L) == 0L; + if (index < 64) return ((lo >> index) & 1L) == X; + return ((hi >> (index & 63)) & 1L) == X; } boolean clueless(int idx) { if (idx < 64) { val test = (1L << idx); - if ((test & bo0) != 0L) { - g[idx] = DASH; - bo0 &= ~test; - return true; - } - return false; + if ((test & lo) == X) return false; + g[idx] = DASH; + + lo &= ~test; } else { val test = (1L << (idx & 63)); - if ((test & bo1) != 0L) { - g[idx] = DASH; - bo1 &= ~test; - return true; - } - return false; + if ((test & hi) == X) return false; + g[idx] = DASH; + + hi &= ~test; } + return true; } static boolean isLetter(byte b) { return (b & 64) != 0; } public boolean isLetterSet(int idx) { return isLetter(g[idx]); } @@ -217,11 +252,17 @@ public record SwedishGenerator(Rng rng) { for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++; return same / SIZED; } - int clueCount() { return Long.bitCount(bo0) + Long.bitCount(bo1); } - + int clueCount() { return Long.bitCount(lo) + Long.bitCount(hi); } + /* for (int k = 0, n = Math.min(MAX_WORD_LENGTH7, (int) (packed >>> 56) * 7); k < n; ) { + if (isClue((int) ((packed >>> k) & 0x7F))) break; + k += 7; + if (k >= MIN_LEN7) return true; + } + return false;*/ + boolean hasRoomForClue(long packed) { return ((packed >>> 56)) > 1L && notClue(packed & 0x7FL) && notClue((packed >>> 7) & 0x7FL); } void forEachSlot(SlotVisitor visitor) { - for (var lo = bo0; lo != 0L; lo &= lo - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(lo)); - for (var hi = bo1; hi != 0L; hi &= hi - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(hi)); + 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)); } } @@ -375,7 +416,7 @@ public record SwedishGenerator(Rng rng) { public int dir() { return key & 15; } public boolean horiz() { return horiz(key); } public int pos(int i) { return offset(packedPos, i); } - public static boolean horiz(int key) { return ((key & 15) & 1) == 0; } + 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); } } @@ -399,14 +440,6 @@ public record SwedishGenerator(Rng rng) { grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); return slots; } - static boolean hasRoomForClue(Grid grid, long packed) { - for (int n = (int) (packed >>> 56) * 7, k = 0; k < n && k < MAX_WORD_LENGTH7; ) { - if (grid.isClue((int) ((packed >>> k) & 0x7F))) break; - k += 7; - if (k >= MIN_LEN7) return true; - } - return false; - } long maskFitness(Grid grid) { var ctx = CTX.get(); @@ -415,18 +448,18 @@ public record SwedishGenerator(Rng rng) { Arrays.fill(covH, 0, SIZE, 0); Arrays.fill(covV, 0, SIZE, 0); - long lo_cl = grid.bo0, hi_cl = grid.bo1; - long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3); + 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 != 0L; bits &= bits - 1) { - 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 (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); + 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; @@ -445,8 +478,8 @@ public record SwedishGenerator(Rng rng) { var stack = ctx.stack; seen.clear(); - for (int i = 0; i < 65; i+=64) { - for (long bits = (i == 0 ? lo_cl : hi_cl); bits != 0L; bits &= bits - 1) { + 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); if (seen.get(clueIdx)) continue; int size = 0; @@ -454,8 +487,7 @@ public record SwedishGenerator(Rng rng) { seen.set(clueIdx); for (int sp = 1; sp > 0; size++) { long packed = Neighbors9x8.NBR8_PACKED[stack[--sp]]; - int n = (int) (packed >>> 56) * 7; - for (int k = 0; k < n; k += 7) { + for (int k = 0, n = (int) (packed >>> 56) * 7; k < n; k += 7) { int nidx = (int) ((packed >>> k) & 0x7F); if (seen.get(nidx) || grid.notClue(nidx)) continue; seen.set(nidx); @@ -466,9 +498,9 @@ public record SwedishGenerator(Rng rng) { } } - for (int i = 0; i < 65; i+=64) { + for (int i = 0; i < 65; i += 64) { long bits = (i == 0 ? ~lo_cl : (~hi_cl & 0xFFL)); - for (; bits != 0L; bits &= bits - 1) { + for (; bits != X; bits &= bits - 1) { int idx = i + Long.numberOfTrailingZeros(bits); var rci = IT[idx]; if ((4 - rci.nbrCount()) + Long.bitCount(rci.n1() & lo_cl) + Long.bitCount(rci.n2() & hi_cl) >= 3) penalty += 400; @@ -486,11 +518,11 @@ public record SwedishGenerator(Rng rng) { Grid randomMask() { var g = Grid.createEmpty(); for (int placed = 0, guard = 0, idx; placed < TARGET_CLUES && guard < 4000; guard++) { - idx = rng.randint(0, SIZE - 1); + idx = rng.randint(0, SIZE_MIN_1); if (g.isClue(idx)) continue; - var d = OFFSETS[rng.randbyte(1, 4)]; + var d = OFFSETS_FOUR[rng.randint2bit()]; - if (hasRoomForClue(g, d.path()[idx])) { + if (g.hasRoomForClue(d.path()[idx])) { g.setClue(idx, d.dbyte()); placed++; } @@ -498,16 +530,15 @@ public record SwedishGenerator(Rng rng) { return g; } Grid mutate(Grid grid) { - var g = grid.deepCopyGrid(); - var centerIdx = rng.randint(0, SIZE - 1); - - var steps = 4; - for (var k = 0; k < steps; k++) { - var ri = MUTATE_RI[centerIdx][rng.randint(0, 624)]; - + var g = grid.deepCopyGrid(); + int ri; + var bytes = MUTATE_RI[rng.randint(0, SIZE_MIN_1)]; + nbrs_16 d; + for (var k = 0; k < 4; k++) { + ri = bytes[rng.randint(0, 624)]; if (!g.clueless(ri)) { - var d = OFFSETS[rng.randint(1, 4)]; - if (hasRoomForClue(g, d.path()[ri])) g.setClue(ri, d.dbyte()); + d = OFFSETS_FOUR[rng.randint2bit()]; + if (g.hasRoomForClue(d.path()[ri])) g.setClue(ri, d.dbyte()); } } return g; @@ -518,7 +549,7 @@ public record SwedishGenerator(Rng rng) { var nc = Math.cos(theta); var nr = Math.sin(theta); - long bo0 = out.bo0, bo1 = out.bo1; + long bo0 = out.lo, bo1 = out.hi; for (var rci : IT) { int i = rci.i(); if ((rci.cross_r()) * nc + (rci.cross_c()) * nr < 0) { @@ -535,13 +566,13 @@ public record SwedishGenerator(Rng rng) { } } } - out.bo0 = bo0; - out.bo1 = bo1; - for (var lo = out.bo0; lo != 0L; lo &= lo - 1L) clearClues(out, Long.numberOfTrailingZeros(lo)); - for (var hi = out.bo1; hi != 0L; hi &= hi - 1L) clearClues(out, 64 + Long.numberOfTrailingZeros(hi)); + 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)); return out; } - public static void clearClues(Grid out, int idx) { if (!hasRoomForClue(out, OFFSETS[out.digitAt(idx)].path()[idx])) out.clearClue(idx); } + public static void clearClues(Grid out, int idx) { if (!out.hasRoomForClue(OFFSETS[out.digitAt(idx)].path()[idx])) out.clearClue(idx); } Grid hillclimb(Grid start, int limit) { var best = start; @@ -740,8 +771,8 @@ public record SwedishGenerator(Rng rng) { System.out.flush(); } Pick chooseMRV() { - Slot best = null; - CandidateInfo bestInfo = null; + Slot best = null; + CandidateInfo bestInfo = null; int bestScore = -1; for (int i = 0, n = slots.size(); i < n; i++) { var s = slots.get(i);