introduce bitloops

This commit is contained in:
mike
2026-01-12 00:47:38 +01:00
parent 986c2f85a9
commit 84ba4c9c63
7 changed files with 164 additions and 143 deletions

View File

@@ -8,10 +8,10 @@ import precomp.Neighbors9x8.nbrs_8;
import precomp.Neighbors9x8.rci;
import puzzle.Export.Bit;
import puzzle.Export.Bit1029;
import puzzle.Export.DictEntryDTO;
import puzzle.Export.Gridded;
import puzzle.Export.Strings;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -37,6 +37,8 @@ public record SwedishGenerator(Rng rng) {
public CandidateInfo(int n) { this(null, n); }
}
static final CandidateInfo[] CANDIDATES = IntStream.range(0, 10192 << 2).mapToObj(CandidateInfo::new).toArray(CandidateInfo[]::new);
//@formatter:off
@FunctionalInterface interface SlotVisitor { void visit(int key, long packedPos, int len); }
//@formatter:on
@@ -67,6 +69,9 @@ public record SwedishGenerator(Rng rng) {
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) { }
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
// Directions for '1'..'6'
static final nbrs_16[] OFFSETS = Neighbors9x8.OFFSETS;
static final nbrs_16[] OFFSETS_FOUR = Neighbors9x8.OFFSETS_FOUR;
@@ -75,6 +80,7 @@ public record SwedishGenerator(Rng rng) {
static final rci[] IT = Neighbors9x8.IT;
static final long[] INBR8_PACKEDT = Neighbors9x8.NBR8_PACKED;
static final int[][] MUTATE_RI = new int[SIZE][625];
static final long[] NBR8_PACKED = Neighbors9x8.NBR8_PACKED;
static {
for (int i = 0; i < SIZE; i++) {
@@ -87,28 +93,6 @@ 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);
@@ -127,9 +111,9 @@ public record SwedishGenerator(Rng rng) {
HashMap<Integer, Lemma> clueMap,
FillStats stats) {
public FillResult {
public void calcSimpel() {
if (ok) {
clueMap.forEach((k, v) -> stats.simplicity += v.simpel);
clueMap.forEach((k, v) -> stats.simplicity += v.simpel());
stats.simplicity = clueMap.isEmpty() ? 0 : stats.simplicity / clueMap.size();
}
}
@@ -247,9 +231,7 @@ public record SwedishGenerator(Rng rng) {
}
return true;
}
static final byte B0 = (byte) 0;
static final byte B64 = (byte) 64;
static final byte B48 = (byte) 48;
static boolean isLetter(byte b) { return (b & B64) != B0; }
public boolean isLetterSet(int idx) { return isLetter(g[idx]); }
static boolean notDigit(byte b) { return (b & B48) != B48; }
@@ -268,27 +250,11 @@ public record SwedishGenerator(Rng rng) {
}
}
static final class IntList {
int[] a = new int[8];
int n = 0;
void add(int v) {
if (n >= a.length) a = Arrays.copyOf(a, a.length * 2);
a[n++] = v;
}
int size() { return n; }
int[] data() { return a; }
}
static final record IntList(int[] data, int size) { }
static record DictEntry(ArrayList<Lemma> words, IntList[][] pos) {
public DictEntry(int L) {
this(new ArrayList<>(), new IntList[L][26]);
for (var i = 0; i < L; i++) for (var j = 0; j < 26; j++) pos[i][j] = new IntList();
}
}
static record DictEntry(Lemma[] words, IntList[][] pos) { }
public static record Lemma(int index, long word, byte len, short simpel) {
public static record Lemma(int index, long word, byte len) {
static int LEMMA_COUNTER = 0;
static long pack(byte[] b) {
@@ -296,15 +262,17 @@ public record SwedishGenerator(Rng rng) {
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 (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 Lemma(int index, String word) { this(index, pack(word.getBytes(US_ASCII)), (byte) word.length()); }
public Lemma(String word) { this(LEMMA_COUNTER++, word); }
byte byteAt(int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111 | B64); }// word[]; }
int intAt(int idx) { return (int) (((word >>> (idx * 5))) & 0b11111); }// 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); }
int simpel() { return CsvIndexService.simpel(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);
for (var i = 0; i < len; i++) b[i] = (byte) ((word >>> (i * 5)) & 0b11111 | B64);
return new String(b, US_ASCII);
}
}
@@ -314,23 +282,29 @@ public record SwedishGenerator(Rng rng) {
int length) {
public Dict(Lemma[] wordz) {
var index = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE];
Arrays.setAll(index, i -> new DictEntry(i));
var index = new DictEntryDTO[MAX_WORD_LENGTH_PLUS_ONE];
Arrays.setAll(index, i -> new DictEntryDTO(i));
for (var lemma : wordz) {
var L = lemma.len;
var entry = index[L];
var idx = entry.words.size();
entry.words.add(lemma);
var idx = entry.words().size();
entry.words().add(lemma);
for (var i = 0; i < L; i++) {
var letter = lemma.byteAt(i) - 'A';
var letter = lemma.intAt(i) - 1;
if (letter < 0 || letter >= 26) throw new RuntimeException("Illegal letter: " + letter + " in word " + lemma);
entry.pos[i][letter].add(idx);
entry.pos()[i][letter].add(idx);
}
}
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words.size() <= 0) throw new RuntimeException("No words for length " + i);
this(index, Arrays.stream(index).mapToInt(i -> i.words.size()).sum());
for (int i = MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i);
this(Arrays.stream(index).map(i -> new DictEntry(i.words().toArray(Lemma[]::new),
Arrays.stream(i.pos())
.map(ii -> Arrays.stream(ii).map(dto -> new IntList(dto.data(), dto.size()))
.toArray(IntList[]::new))
.toArray(IntList[][]::new)))
.toArray(DictEntry[]::new),
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
}
static Dict loadDict(String wordsPath) {
try {
@@ -394,12 +368,12 @@ public record SwedishGenerator(Rng rng) {
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
var d = grid.digitAt(idx);
var packed = OFFSETS[d].path()[idx];
long packedPos = 0;
long packedPos = 0L;
int k = 0;
for (int n = (int) (packed >>> 56), iidx; k < n && k < MAX_WORD_LENGTH; k++) {
iidx = (int) ((packed >>> (k * 7)) & 0x7F);
for (long n = (packed >>> 56), offset = 0L, iidx; k < n; k++, offset += 7L) {
iidx = ((packed >>> offset) & 0x7FL);
if (grid.isClue(iidx)) break;
packedPos |= (long) iidx << (k * 7);
packedPos |= iidx << offset;
}
if (k > 0) {
visitor.visit((idx << 4) | d, packedPos, k);
@@ -412,52 +386,68 @@ public record SwedishGenerator(Rng rng) {
return slots;
}
static long fitting(Grid grid, int clueIdx, int[] covH, int[] covV) {
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;
horiz[idx] += 1;
}
if (k > 0) {
if (k < MIN_LEN7) return 8001L;
return 1L;
}
return 0L;
}
long maskFitness(Grid grid) {
var ctx = CTX.get();
var covH = ctx.covH;
var covV = ctx.covV;
Arrays.fill(covH, 0, SIZE, 0);
Arrays.fill(covV, 0, SIZE, 0);
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
boolean hasSlots = false;
int clueIdx;
long lo_cl = grid.lo, hi_cl = grid.hi;
long penalty = 0L;
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;
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;
horiz[idx] += 1;
}
if (k > 0) {
hasSlots = true;
if (k < MIN_LEN7) penalty += 8000;
penalty |= 1;
}
}
}
if (!hasSlots) return 1_000_000_000L;
if ((penalty & 1) == 0) return 1_000_000_000L;
penalty = (penalty & ~3L) + (((long) Math.abs(grid.clueCount() - TARGET_CLUES)) << 3);
var seen = ctx.seen;
var stack = ctx.stack;
seen.clear();
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);
clueIdx = i + Long.numberOfTrailingZeros(bits);
if (seen.get(clueIdx)) continue;
int size = 0;
stack[0] = clueIdx;
seen.set(clueIdx);
for (int sp = 1; sp > 0; size++) {
long packed = Neighbors9x8.NBR8_PACKED[stack[--sp]];
long packed = NBR8_PACKED[stack[--sp]];
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;
@@ -472,11 +462,11 @@ 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 idx = i + Long.numberOfTrailingZeros(bits);
var rci = IT[idx];
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[idx];
var v = covV[idx];
var h = covH[clueIdx];
var v = covV[clueIdx];
if (h == 0 && v == 0) penalty += 1500;
else if (h > 0 && v > 0) { /* ok */ } else if (h + v == 1) penalty += 200;
else penalty += 600;
@@ -650,7 +640,7 @@ 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;
@@ -665,7 +655,7 @@ public record SwedishGenerator(Rng rng) {
}
if (listCount == 0) {
return CANDIDATES[entry.words.size()];
return CANDIDATES[entry.words.length];
}
// Sort constraints by size to optimize intersection
@@ -801,26 +791,26 @@ public record SwedishGenerator(Rng rng) {
double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * L);
var idx = idxs[idxInArray];
var w = entry.words.get(idx);
var w = entry.words[idx];
if (used.get(w.index())) continue;
if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index());
used.set(w.index);
assigned.put(k, w);
if (backtrack(depth + 1)) return true;
assigned.remove(k);
used.clear(w.index());
used.clear(w.index);
s.undoPlace(grid, ctx.undo[depth]);
}
stats.backtracks++;
return false;
}
var N = entry.words.size();
var N = entry.words.length;
if (N == 0) {
stats.backtracks++;
return false;
@@ -830,13 +820,13 @@ public record SwedishGenerator(Rng rng) {
for (var t = 0; t < tries; t++) {
double r = rng.nextFloat();
int idxInArray = (int) (r * r * r * N);
var w = entry.words.get(idxInArray);
var w = entry.words[idxInArray];
if (used.get(w.index())) continue;
if (used.get(w.index)) continue;
if (!placeWord(grid, s, w, ctx.undo, depth)) continue;
used.set(w.index());
used.set(w.index);
assigned.put(k, w);
if (backtrack(depth + 1)) return true;