introduce bitloops

This commit is contained in:
mike
2026-01-12 12:02:16 +01:00
parent b6351a6fb2
commit a0862fcc43
4 changed files with 170 additions and 101 deletions

View File

@@ -67,6 +67,10 @@ public record Export() {
if ((bitIndex & 64) == 0) this.l1 |= 1L << bitIndex; if ((bitIndex & 64) == 0) this.l1 |= 1L << bitIndex;
else this.l2 |= 1L << (bitIndex & 63); else this.l2 |= 1L << (bitIndex & 63);
} }
public void or(long lo, long hi) {
this.l1 |= lo;
this.l2 |= hi;
}
public void clear() { public void clear() {
l1 = 0L; l1 = 0L;
l2 = 0L; l2 = 0L;
@@ -142,10 +146,10 @@ public record Export() {
var g = filled().grid(); var g = filled().grid();
var placed = new ArrayList<Placed>(); var placed = new ArrayList<Placed>();
var clueMap = filled().clueMap(); var clueMap = filled().clueMap();
g.grid().forEachSlot((int key, long packedPos, int len) -> { g.grid().forEachSlot((int key, long lo, long hi) -> {
var word = clueMap.get(key); var word = clueMap.get(key);
if (word != null) { if (word != null) {
var p = extractPlacedFromSlot(Slot.from(key, packedPos, len), word); var p = extractPlacedFromSlot(Slot.from(key, lo, hi), word);
if (p != null) placed.add(p); if (p != null) placed.add(p);
} }
}); });

View File

@@ -36,7 +36,7 @@ public record SwedishGenerator(Rng rng) {
// static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new); // static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
//@formatter:off //@formatter:off
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); } @FunctionalInterface interface SlotVisitor { void visit(int key, long lo, long hi); }
//@formatter:on //@formatter:on
static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L; static final long GT_1_OFFSET_53_BIT = 0x3E00000000000000L;
static final long X = 0L; static final long X = 0L;
@@ -307,28 +307,68 @@ public record SwedishGenerator(Rng rng) {
} }
} }
static record Slot(int key, long packedPos, long packedPoslo, long packedPoshi) { static record Slot(int key, long lo, long hi) {
static final int BIT_FOR_DIR = 3; static final int BIT_FOR_DIR = 3;
static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56), 0L, 0L); } static Slot from(int key, long lo, long hi) { return new Slot(key, lo, hi); }
void undoPlace(Grid grid, int mask) { for (int i = 0, len = len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clearletter(pos(i)); } void undoPlace(Grid grid, int mask) {
public int len() { return (int) (packedPos >>> 56); } boolean increasing = (dir() == 2 || dir() == 3);
int i = 0;
if (increasing) {
for (long b = lo; b != 0; b &= b - 1, i++) if ((mask & (1 << i)) != 0) grid.clearletter(Long.numberOfTrailingZeros(b));
for (long b = hi; b != 0; b &= b - 1, i++) if ((mask & (1 << i)) != 0) grid.clearletter(64 | Long.numberOfTrailingZeros(b));
} else {
for (long b = hi; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
if ((mask & (1 << i)) != 0) grid.clearletter(64 | msb);
b &= ~(1L << msb);
}
for (long b = lo; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
if ((mask & (1 << i)) != 0) grid.clearletter(msb);
b &= ~(1L << msb);
}
}
}
public int len() { return Long.bitCount(lo) + Long.bitCount(hi); }
public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); } public int clueR() { return Grid.r((key >>> BIT_FOR_DIR)); }
public int clueIndex() { return key >>> BIT_FOR_DIR; } public int clueIndex() { return key >>> BIT_FOR_DIR; }
public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); } public int clueC() { return Grid.c((key >>> BIT_FOR_DIR)); }
public int dir() { return key & 7; } public int dir() { return key & 7; }
public boolean horiz() { return horiz(key); } public boolean horiz() { return horiz(dir()); }
public int pos(int i) { return offset(packedPos, i); } public int pos(int i) {
public static boolean horiz(int key) { return (key & 1) == 0; } boolean increasing = (dir() == 2 || dir() == 3);
public static int offset(long packedPos, int i) { return (int) ((packedPos >>> (i * 7)) & 127); } if (increasing) {
int bcLo = Long.bitCount(lo);
if (i < bcLo) {
long temp = lo;
for (int k = 0; k < i; k++) temp &= temp - 1;
return Long.numberOfTrailingZeros(temp);
} else {
i -= bcLo;
long temp = hi;
for (int k = 0; k < i; k++) temp &= temp - 1;
return 64 | Long.numberOfTrailingZeros(temp);
}
} else {
int bcHi = Long.bitCount(hi);
if (i < bcHi) {
long temp = hi;
for (int k = 0; k < i; k++) temp &= ~(1L << (63 - Long.numberOfLeadingZeros(temp)));
return 64 | (63 - Long.numberOfLeadingZeros(temp));
} else {
i -= bcHi;
long temp = lo;
for (int k = 0; k < i; k++) temp &= ~(1L << (63 - Long.numberOfLeadingZeros(temp)));
return 63 - Long.numberOfLeadingZeros(temp);
}
}
}
public static boolean horiz(int d) { return (d & 1) == 0; }
public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; } public static int packSlotDir(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
} }
interface SlotVisitor2 { private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
void visitMasks(int slotDir, long loMask, long hiMask);
}
private static void processSlot(Grid grid, SlotVisitor2 visitor, int idx) {
int d = grid.digitAt(idx); // 1..4 int d = grid.digitAt(idx); // 1..4
int di = d - 1; // 0..3 int di = d - 1; // 0..3
int key = (idx << 2) | di; int key = (idx << 2) | di;
@@ -370,28 +410,13 @@ public record SwedishGenerator(Rng rng) {
} }
if ((rayLo | rayHi) != 0) { if ((rayLo | rayHi) != 0) {
visitor.visitMasks(Slot.packSlotDir(idx, d), rayLo, rayHi); visitor.visit(Slot.packSlotDir(idx, d), rayLo, rayHi);
}
}
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
var d = grid.digitAt(idx);
var packed = OFFSETS_D_IDX[(d - 1) | (idx << 2)];
long packedPos = 0L;
int k = 0;
for (long n = (packed >>> 56), offset = 0L, iidx; k < n; k++, offset += 7L) {
iidx = ((packed >>> offset) & 0x7FL);
if (grid.isClue(iidx)) break;
packedPos |= iidx << offset;
}
if (k > 0) {
visitor.visit(Slot.packSlotDir(idx, d), packedPos, k);
} }
} }
static ArrayList<Slot> extractSlots(Grid grid) { static ArrayList<Slot> extractSlots(Grid grid) {
var slots = new ArrayList<Slot>(32); var slots = new ArrayList<Slot>(32);
grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len))); grid.forEachSlot((key, lo, hi) -> slots.add(Slot.from(key, lo, hi)));
return slots; return slots;
} }
@@ -409,20 +434,28 @@ public record SwedishGenerator(Rng rng) {
for (int i = 0; i < 65; i += 64) { for (int i = 0; i < 65; i += 64) {
for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) { for (long bits = (i == 0 ? lo_cl : hi_cl); bits != X; bits &= bits - 1) {
int clueIdx = i | Long.numberOfTrailingZeros(bits); int clueIdx = i | Long.numberOfTrailingZeros(bits);
var d = grid.digitAt(clueIdx); int d = grid.digitAt(clueIdx);
long packed = OFFSETS_D_IDX[(d - 1) | (clueIdx << 2)]; int di = d - 1;
int key = (clueIdx << 2) | di;
int n = (int) (packed >>> 56) * 7, k, idx; long rLo = PATH_LO[key], rHi = PATH_HI[key];
var horiz = Slot.horiz(d) ? covH : covV; long hLo = rLo & lo_cl, hHi = rHi & hi_cl;
for (k = 0; k < n; k += 7) { if (d == 2 || d == 3) {
idx = (int) ((packed >>> (k)) & 0x7F); if (hLo != 0) { rLo &= ((1L << Long.numberOfTrailingZeros(hLo)) - 1); rHi = 0; }
if (grid.isClue(idx)) break; else if (hHi != 0) { rHi &= ((1L << Long.numberOfTrailingZeros(hHi)) - 1); }
horiz.set(idx); } else {
if (hHi != 0) {
int msb = 63 - Long.numberOfLeadingZeros(hHi);
rHi &= ~((1L << msb << 1) - 1); rLo = 0;
} else if (hLo != 0) {
int msb = 63 - Long.numberOfLeadingZeros(hLo);
rLo &= ~((1L << msb << 1) - 1);
}
} }
if (k > 0) { if ((rLo | rHi) != 0) {
hasSlots = true; hasSlots = true;
if (k < MIN_LEN7) penalty += 8000; if (Slot.horiz(d)) covH.or(rLo, rHi); else covV.or(rLo, rHi);
if ((Long.bitCount(rLo) + Long.bitCount(rHi)) < MIN_LEN) penalty += 8000;
} else { } else {
penalty += 25000; penalty += 25000;
} }
@@ -644,37 +677,73 @@ public record SwedishGenerator(Rng rng) {
static long patternForSlot(Grid grid, Slot s) { static long patternForSlot(Grid grid, Slot s) {
long p = 0; long p = 0;
for (int i = 0, len = s.len(); i < len; i++) { int i = 0;
byte ch = grid.byteAt(s.pos(i)); boolean increasing = (s.dir() == 2 || s.dir() == 3);
if (isLetter(ch)) { if (increasing) {
p |= ((long) (ch & 31)) << (i * 5); for (long b = s.lo; b != 0; b &= b - 1, i++) {
byte ch = grid.byteAt(Long.numberOfTrailingZeros(b));
if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5);
}
for (long b = s.hi; b != 0; b &= b - 1, i++) {
byte ch = grid.byteAt(64 | Long.numberOfTrailingZeros(b));
if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5);
}
} else {
for (long b = s.hi; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
byte ch = grid.byteAt(64 | msb);
if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5);
b &= ~(1L << msb);
}
for (long b = s.lo; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
byte ch = grid.byteAt(msb);
if (isLetter(ch)) p |= ((long) (ch & 31)) << (i * 5);
b &= ~(1L << msb);
} }
} }
return p; return p;
} }
static int slotScore(int[] count, Slot s) { static int slotScore(int[] count, Slot s) {
int cross = 0, len = s.len(); int cross = 0;
for (int i = 0; i < len; i++) cross += (count[s.pos(i)] - 1); for (long b = s.lo; b != 0; b &= b - 1) cross += (count[Long.numberOfTrailingZeros(b)] - 1);
return cross * 10 + len; for (long b = s.hi; b != 0; b &= b - 1) cross += (count[64 | Long.numberOfTrailingZeros(b)] - 1);
return cross * 10 + s.len();
} }
static boolean placeWord(Grid grid, Slot s, long w, int[] undoBuffer, int offset) { static boolean placeWord(Grid grid, Slot s, long w, int[] undoBuffer, int offset) {
int mask = 0; int mask = 0;
byte cur, ch; boolean increasing = (s.dir() == 2 || s.dir() == 3);
for (int i = 0, leng = s.len(), idx; i < leng; i++) { int i = 0;
idx = s.pos(i); if (increasing) {
cur = grid.byteAt(idx); for (long b = s.lo; b != 0; b &= b - 1, i++) {
ch = Lemma.byteAt(w, i); int idx = Long.numberOfTrailingZeros(b);
if (cur == DASH) { byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i);
mask |= (1 << i); if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); }
grid.setLetter(idx, ch); else if (cur != ch) { s.undoPlace(grid, mask); return false; }
} else if (cur != ch) { }
for (var j = 0; j < i; j++) { for (long b = s.hi; b != 0; b &= b - 1, i++) {
if ((mask & (1 << j)) != 0) { int idx = 64 | Long.numberOfTrailingZeros(b);
grid.clearletter(s.pos(j)); byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i);
} if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); }
} else if (cur != ch) { s.undoPlace(grid, mask); return false; }
return false; }
} else {
for (long b = s.hi; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
int idx = 64 | msb;
byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i);
if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); }
else if (cur != ch) { s.undoPlace(grid, mask); return false; }
b &= ~(1L << msb);
}
for (long b = s.lo; b != 0; i++) {
int msb = 63 - Long.numberOfLeadingZeros(b);
int idx = msb;
byte cur = grid.byteAt(idx), ch = Lemma.byteAt(w, i);
if (cur == DASH) { mask |= (1 << i); grid.setLetter(idx, ch); }
else if (cur != ch) { s.undoPlace(grid, mask); return false; }
b &= ~(1L << msb);
} }
} }
undoBuffer[offset] = mask; undoBuffer[offset] = mask;
@@ -764,7 +833,10 @@ public record SwedishGenerator(Rng rng) {
Arrays.fill(count, 0, SIZE, 0); Arrays.fill(count, 0, SIZE, 0);
val slots = extractSlots(grid); val slots = extractSlots(grid);
for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++; for (var s : slots) {
for (long b = s.lo; b != 0; b &= b - 1) count[Long.numberOfTrailingZeros(b)]++;
for (long b = s.hi; b != 0; b &= b - 1) count[64 | Long.numberOfTrailingZeros(b)]++;
}
val slotScores = new int[slots.size()]; val slotScores = new int[slots.size()];
for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i)); for (int i = 0; i < slots.size(); i++) slotScores[i] = slotScore(count, slots.get(i));

View File

@@ -37,12 +37,6 @@ public class MainTest {
@Test @Test
void testStaticSlotMethods() { void testStaticSlotMethods() {
// Test static offset extraction
// packedPos: offset(1, 1) at index 0, offset(2, 2) at index 1
var packedPos = ((long) Grid.offset(1, 1)) | (((long) Grid.offset(2, 2)) << 7);
assertEquals(Grid.offset(1, 1), Slot.offset(packedPos, 0));
assertEquals(Grid.offset(2, 2), Slot.offset(packedPos, 1));
// Test static horiz // Test static horiz
// dir 2 (right) is horizontal // dir 2 (right) is horizontal
assertTrue(Slot.horiz(2)); assertTrue(Slot.horiz(2));
@@ -56,21 +50,20 @@ public class MainTest {
grid.setClue(0, (byte) '2'); // right grid.setClue(0, (byte) '2'); // right
var count = new AtomicInteger(0); var count = new AtomicInteger(0);
grid.forEachSlot((key, packedPos, len) -> { grid.forEachSlot((key, lo, hi) -> {
count.incrementAndGet(); count.incrementAndGet();
assertEquals(8, len); assertEquals(8, Long.bitCount(lo) + Long.bitCount(hi));
assertEquals(0, Grid.r(Slot.offset(packedPos, 0))); assertEquals(0, Grid.r(Long.numberOfTrailingZeros(lo)));
assertEquals(1, Grid.c(Slot.offset(packedPos, 0))); assertEquals(1, Grid.c(Long.numberOfTrailingZeros(lo)));
}); });
assertEquals(1, count.get()); assertEquals(1, count.get());
} }
@Test @Test
public void testHoriz() { public void testHoriz() {
assertTrue(Slot.from(2, 0L, 1).horiz()); assertTrue(Slot.from(2, 0L, 0L).horiz());
assertTrue(Slot.from(4, 0L, 1).horiz()); assertTrue(Slot.from(4, 0L, 0L).horiz());
assertFalse(Slot.from(1, 0L, 1).horiz()); assertFalse(Slot.from(1, 0L, 0L).horiz());
assertFalse(Slot.from(3, 0L, 1).horiz()); assertFalse(Slot.from(3, 0L, 0L).horiz());
assertFalse(Slot.from(5, 0L, 1).horiz());
} }
@Test @Test
public void testGridBasics() { public void testGridBasics() {

View File

@@ -15,7 +15,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testPatternForSlotAllLetters() { void testPatternForSlotAllLetters() {
var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C var grid = new Grid(new byte[]{ 65, 66, 67 }); // A B C
var slot = Slot.from(18, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var slot = Slot.from(18, 7L, 0L);
long pattern = patternForSlot(grid, slot); long pattern = patternForSlot(grid, slot);
assertEquals(1 | (2 << 5) | (3 << 10), pattern); assertEquals(1 | (2 << 5) | (3 << 10), pattern);
@@ -24,7 +24,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testPatternForSlotMixed() { void testPatternForSlotMixed() {
var grid = new Grid(new byte[]{ 65, DASH, 67 }); // A - C var grid = new Grid(new byte[]{ 65, DASH, 67 }); // A - C
var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var slot = Slot.from(1 << 3 | 2, 7L, 0L);
long pattern = patternForSlot(grid, slot); long pattern = patternForSlot(grid, slot);
assertEquals(1 | (0 << 5) | (3 << 10), pattern); assertEquals(1 | (0 << 5) | (3 << 10), pattern);
@@ -33,7 +33,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testPatternForSlotAllDashes() { void testPatternForSlotAllDashes() {
var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - - var grid = new Grid(new byte[]{ DASH, DASH, DASH }); // - - -
var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var slot = Slot.from(1 << 3 | 2, 7L, 0L);
long pattern = patternForSlot(grid, slot); long pattern = patternForSlot(grid, slot);
assertEquals(0, pattern); assertEquals(0, pattern);
@@ -42,7 +42,7 @@ public class SwedishGeneratorTest {
@Test @Test
void testPatternForSlotSingleLetter() { void testPatternForSlotSingleLetter() {
var grid = new Grid(new byte[]{ 65, DASH, DASH }); // A - - var grid = new Grid(new byte[]{ 65, DASH, DASH }); // A - -
var slot = Slot.from(1 << 4 | 2, ((long) 0) | ((long) 1 << 7) | ((long) 2 << 14), 3); var slot = Slot.from(1 << 3 | 2, 7L, 0L);
long pattern = patternForSlot(grid, slot); long pattern = patternForSlot(grid, slot);
assertEquals(1, pattern); assertEquals(1, pattern);
@@ -136,21 +136,21 @@ public class SwedishGeneratorTest {
// key = (r << 8) | (c << 4) | d // key = (r << 8) | (c << 4) | d
var offset = Grid.offset(2, 3); var offset = Grid.offset(2, 3);
System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset); System.out.println("[DEBUG_LOG] Grid.offset(2, 3) = " + offset);
var key = (offset << Slot.BIT_FOR_DIR) | 5; var key = (offset << Slot.BIT_FOR_DIR) | 3;
System.out.println("[DEBUG_LOG] key = " + key); System.out.println("[DEBUG_LOG] key = " + key);
long packedPos = 0; long lo = 0;
// pos 0: (2, 5) // pos 0: (2, 5)
packedPos |= Grid.offset(2, 5); lo |= 1L << Grid.offset(2, 5);
// pos 1: (3, 5) // pos 1: (3, 5)
packedPos |= (long) Grid.offset(3, 5) << 7; lo |= 1L << Grid.offset(3, 5);
// pos 2: (4, 5) // pos 2: (4, 5)
packedPos |= (long) Grid.offset(4, 5) << 14; lo |= 1L << Grid.offset(4, 5);
var s = Slot.from(key, packedPos, 3); var s = Slot.from(key, lo, 0L);
System.out.println("[DEBUG_LOG] s.dir() = " + s.dir()); System.out.println("[DEBUG_LOG] s.dir() = " + s.dir());
assertEquals(2, s.clueR()); assertEquals(2, s.clueR());
assertEquals(3, s.clueC()); assertEquals(3, s.clueC());
assertEquals(5, s.dir()); assertEquals(3, s.dir());
assertFalse(s.horiz()); assertFalse(s.horiz());
assertEquals(2, Grid.r(s.pos(0))); assertEquals(2, Grid.r(s.pos(0)));
assertEquals(3, Grid.r(s.pos(1))); assertEquals(3, Grid.r(s.pos(1)));
@@ -305,10 +305,9 @@ public class SwedishGeneratorTest {
void testPlaceWord() { void testPlaceWord() {
var grid = Grid.createEmpty(); var grid = Grid.createEmpty();
// Slot at (0,0) length 3, horizontal (right) // Slot at (0,0) length 3, horizontal (right)
// key = (r << 8) | (c << 4) | d. Here we just need a valid slot for placeWord. var key = (Grid.offset(0,0) << Slot.BIT_FOR_DIR) | 2;
// r(i) and c(i) are used by placeWord. var lo = (1L << Grid.offset(0, 0)) | (1L << Grid.offset(0, 1)) | (1L << Grid.offset(0, 2));
var packedPos = ((long) Grid.offset(0, 0)) | (((long) Grid.offset(0, 1)) << 7) | (((long) Grid.offset(0, 2)) << 14); var s = Slot.from(key, lo, 0L);
var s = Slot.from(0, packedPos, 3);
var w1 = Lemma.from("ABC"); var w1 = Lemma.from("ABC");
var undoBuffer = new int[10]; var undoBuffer = new int[10];
@@ -345,8 +344,9 @@ public class SwedishGeneratorTest {
void testBacktrackingHelpers() { void testBacktrackingHelpers() {
var grid = Grid.createEmpty(); var grid = Grid.createEmpty();
// Slot at 0,1 length 2 // Slot at 0,1 length 2
var packedPos = ((long) Grid.offset(0, 1)) | (((long) Grid.offset(0, 2)) << 7); var key = (Grid.offset(0,0) << Slot.BIT_FOR_DIR) | 2;
var s = Slot.from((0 << 8) | (1 << 4) | 2, packedPos, 2); var lo = (1L << Grid.offset(0, 1)) | (1L << Grid.offset(0, 2));
var s = Slot.from(key, lo, 0L);
var w = Lemma.from("AZ"); var w = Lemma.from("AZ");
var undoBuffer = new int[10]; var undoBuffer = new int[10];