introduce bitloops

This commit is contained in:
mike
2026-01-19 16:31:33 +01:00
parent 37581d15b4
commit 1fa112ab65
14 changed files with 393 additions and 455 deletions

View File

@@ -1,12 +1,13 @@
package puzzle;
import static puzzle.SwedishGenerator.*;
public final class DictData {
private DictData() {}
public static final SwedishGenerator.Dict DICT = build();
public static final Dict DICT = build();
private static SwedishGenerator.Dict build() {
SwedishGenerator.DictEntry[] idx = new SwedishGenerator.DictEntry[SwedishGenerator.MAX_WORD_LENGTH_PLUS_ONE];
private static Dict build() {
DictEntry[] idx = new DictEntry[MAX_WORD_LENGTH_PLUS_ONE];
idx[2] = DictDataL2.entry();
idx[3] = DictDataL3.entry();
idx[4] = DictDataL4.entry();
@@ -14,6 +15,6 @@ public final class DictData {
idx[6] = DictDataL6.entry();
idx[7] = DictDataL7.entry();
idx[8] = DictDataL8.entry();
return new SwedishGenerator.Dict(idx, 40670);
return new Dict(idx, 40670);
}
}

View File

@@ -1,5 +1,6 @@
package puzzle;
import static puzzle.SwedishGenerator.*;
public final class DictDataL3 {
private DictDataL3() {}
@@ -17,11 +18,11 @@ public final class DictDataL3 {
return DictDataL3P0.get();
}
public static SwedishGenerator.DictEntry entry() {
public static DictEntry entry() {
long[] wds = words();
long[] flat = posFlat();
long[][] pos = reshape(flat, ROWS, COLS);
return new SwedishGenerator.DictEntry(wds, pos, wds.length, (wds.length + 63) >>> 6);
return new DictEntry(wds, pos, wds.length, (wds.length + 63) >>> 6);
}
private static int copy(long[] dst, int at, long[] src) {

View File

@@ -10,4 +10,7 @@ public final class Config {
public static final int MAX_LEN = ${MAX_LEN};
public static final int PUZZLE_ROWS = ${PUZZLE_ROWS};
public static final int PUZZLE_COLS = ${PUZZLE_COLS};
public static final int PUZZLE_SIZE = PUZZLE_ROWS*PUZZLE_COLS;
public static final int MAX_WORD_LENGTH = PUZZLE_ROWS;
public static final int MAX_WORD_LENGTH_MIN_1 = PUZZLE_ROWS-1;
}

View File

@@ -1,5 +1,6 @@
package puzzle;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@@ -13,13 +14,14 @@ import puzzle.SwedishGenerator.DictEntry;
import puzzle.SwedishGenerator.FillResult;
import puzzle.SwedishGenerator.Grid;
import puzzle.SwedishGenerator.Slotinfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static puzzle.Export.Clue.DOWN;
import static puzzle.Export.Clue.RIGHT;
import static puzzle.Masker.Clues.createEmpty;
import static puzzle.SwedishGenerator.R;
import static puzzle.SwedishGenerator.Lemma;
import static puzzle.Masker.Slot;
@@ -37,42 +39,78 @@ import static puzzle.SwedishGenerator.X;
*/
public record Export() {
record Strings() {
static String padRight(String s, int n) {
if (s.length() >= n) return s;
return s + " ".repeat(n - s.length());
}
public static final ThreadLocal<byte[]> BYTES = ThreadLocal.withInitial(() -> new byte[SwedishGenerator.MAX_WORD_LENGTH]);
static final byte CLUE_DOWN = 0;
static final byte CLUE_RIGHT = 1;
static final byte CLUE_UP = 2;
static final byte CLUE_LEFT = 3;
static final byte CLUE_LEFT_TOP = 4;
static int HI(int in) { return in | 64; }
static char LETTER(int in) { return (char) (in | 64); }
static char CLUE_CHAR(int s) { return (char) (s | 48); }
static int INDEX_ROW(int idx) { return idx & 7; }
static int INDEX_COL(int idx) { return idx >>> 3; }
static int INDEX(int r, int cols, int c) { return r * cols + c; }
@AllArgsConstructor
enum Clue {
DOWN(CLUE_DOWN),
RIGHT(CLUE_RIGHT),
UP(CLUE_UP),
LEFT(CLUE_LEFT);
final byte dir;
}
static final String INIT = IntStream.range(0, R).mapToObj(l_ -> " ").collect(Collectors.joining("\n"));
record Strings() {
static String padRight(String s, int n) { return s.length() >= n ? s : s + " ".repeat(n - s.length()); }
}
static final String INIT = IntStream.range(0, Config.PUZZLE_ROWS).mapToObj(l_ -> " ").collect(Collectors.joining("\n"));
public record ClueAt(int index, int clue) { }
public record Clued(@Delegate Clues c) {
public Clued deepCopyGrid() { return new Clued(new Clues(c.lo, c.hi, c.vlo, c.vhi, c.rlo, c.rhi, c.xlo, c.xhi)); }
public static Clued parse(String s) {
var c = createEmpty();
var lines = s.split("\n");
for (int r = 0; r < Math.min(lines.length, R); r++) {
var line = lines[r];
for (int col = 0; col < Math.min(line.length(), C); col++) {
char ch = line.charAt(col);
if (ch >= '0' && ch <= '4') {
int idx = Masker.offset(r, col);
byte dir = (byte) (ch - '0');
if ((idx & 64) == 0) c.setClueLo(1L << idx, dir);
else c.setClueHi(1L << (idx & 63), dir);
}
}
}
return new Clued(c);
}
String gridToString() {
var sb = new StringBuilder(INIT);
forEachSlot((s, _, _) -> {
val idx = Slot.clueIndex(s);
val r = idx & 7;
val c = idx >>> 3;
val dir = Slot.dir(s);
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
sb.setCharAt(INDEX(INDEX_ROW(idx), C + 1, INDEX_COL(idx)), CLUE_CHAR(dir));
});
return sb.toString();
}
public Stream<ClueAt> stream() {
val stream = Stream.<ClueAt>builder();
for (var l = c.lo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 1));
for (var l = c.lo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 0));
for (var l = c.lo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 2));
for (var l = c.lo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), 3));
for (var h = c.hi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 1));
for (var h = c.hi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(64 | Long.numberOfTrailingZeros(h), 0));
for (var h = c.hi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 2));
for (var h = c.hi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt((64 | Long.numberOfTrailingZeros(h)), 3));
for (var l = c.lo & ~c.xlo & ~c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), RIGHT.dir));
for (var l = c.lo & ~c.xlo & ~c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), DOWN.dir));
for (var l = c.lo & ~c.xlo & c.rlo & ~c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_UP));
for (var l = c.lo & ~c.xlo & c.rlo & c.vlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT));
for (var l = c.lo & c.xlo; l != X; l &= l - 1) stream.accept(new ClueAt(Long.numberOfTrailingZeros(l), CLUE_LEFT_TOP));
for (var h = c.hi & ~c.xhi & ~c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_RIGHT));
for (var h = c.hi & ~c.xhi & ~c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_DOWN));
for (var h = c.hi & ~c.xhi & c.rhi & ~c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_UP));
for (var h = c.hi & ~c.xhi & c.rhi & c.vhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT));
for (var h = c.hi & c.xhi; h != X; h &= h - 1) stream.accept(new ClueAt(HI(Long.numberOfTrailingZeros(h)), CLUE_LEFT_TOP));
return stream.build();
}
@@ -83,29 +121,51 @@ public record Export() {
record LetterAt(int index, byte letter) {
public char human() {
return (char) (letter | 64);
}
public int row() { return INDEX_ROW(index); }
public int col() { return INDEX_COL(index); }
public char human() { return LETTER(letter); }
static LetterAt from(int index, byte[] bytes) { return new LetterAt(index, bytes[index]); }
public int index(int cols) { return (row() * cols) + col(); }
}
void visit(int index, byte letter);
default void visit(int index, byte[] letters) { visit(index, letters[index]); }
}
record Gridded(@Delegate Grid grid) {
record Gridded(@Delegate Grid grid, Clues cl) {
public Gridded(Clues clues) { this(clues.toGrid(), clues); }
public Stream<LetterAt> stream(Clues clues) {
val stream = Stream.<LetterAt>builder();
for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) stream.accept(LetterAt.from(Long.numberOfTrailingZeros(l), grid.g));
for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) stream.accept(LetterAt.from(64 | Long.numberOfTrailingZeros(h), grid.g));
return stream.build();
}
public void forEachLetter(Clues clues, LetterVisit visitor) {
for (var l = grid.lo & ~clues.lo; l != X; l &= l - 1) visitor.visit(Long.numberOfTrailingZeros(l), grid.g);
for (var h = grid.hi & ~clues.hi & 0xFF; h != X; h &= h - 1) visitor.visit(64 | Long.numberOfTrailingZeros(h), grid.g);
String gridToString(Clues clues) {
var sb = new StringBuilder(INIT);
clues.forEachSlot((s, _, _) -> {
val idx = Slot.clueIndex(s);
val r = idx & 7;
val c = idx >>> 3;
val dir = Slot.dir(s);
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
});
stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human()));
return sb.toString();
}
public static IntStream walk(byte base, long lo, long hi) {
public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) {
var sb = new StringBuilder(INIT);
clues.forEachSlot((s, l, a) -> {
val idx = Slot.clueIndex(s);
val r = idx & 7;
val c = idx >>> 3;
val dir = Slot.dir(s);
sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48))));
});
stream(clues).forEach((l) -> sb.setCharAt(l.index(C + 1), l.human()));
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
}
public static IntStream cellWalk(byte base, long lo, long hi) {
if (Slotinfo.increasing(base)) {
return IntStream.concat(
IntStream.generate(new IntSupplier() {
@@ -136,9 +196,8 @@ public record Export() {
@Override
public int getAsInt() {
int msb = 63 - Long.numberOfLeadingZeros(temp);
int res = 64 | msb;
temp &= ~(1L << msb);
return res;
return 64 | msb;
}
}).limit(Long.bitCount(hi)),
IntStream.generate(new IntSupplier() {
@@ -147,65 +206,33 @@ public record Export() {
@Override
public int getAsInt() {
int msb = 63 - Long.numberOfLeadingZeros(temp);
int res = msb;
temp &= ~(1L << msb);
return res;
return msb;
}
}).limit(Long.bitCount(lo)));
}
}
String gridToString(Clues clues) {
var sb = new StringBuilder(INIT);
clues.forEachSlot((s, _, _) -> {
val idx = Slot.clueIndex(s);
val r = idx & 7;
val c = idx >>> 3;
val dir = Slot.dir(s);
sb.setCharAt(r * (C + 1) + c, (char) (dir | 48));
});
forEachLetter(clues, (idx, letter) -> {
val r = idx & 7;
val c = idx >>> 3;
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
});
return sb.toString();
}
public String renderHuman(Clues clues) {
return String.join("\n", exportGrid(clues, _ -> ' ', '#'));
}
public String renderHuman(Clues clues) { return String.join("\n", exportGrid(clues, _ -> ' ', '#')); }
@FunctionalInterface
interface Replacar {
record Cell(Grid grid, Clues clues, int index, byte data) { }
char replace(Cell c);
}
public String[] exportGrid(Clues clues, Replacar clueChar, char emptyFallback) {
var sb = new StringBuilder(INIT);
clues.forEachSlot((s, l, a) -> {
val idx = Slot.clueIndex(s);
val r = idx & 7;
val c = idx >>> 3;
val dir = Slot.dir(s);
sb.setCharAt(r * (C + 1) + c, clueChar.replace(new Cell(grid, clues, idx, (byte) (dir | 48))));
});
forEachLetter(clues, (idx, letter) -> {
val r = idx & 7;
val c = idx >>> 3;
sb.setCharAt(r * (C + 1) + c, (char) (letter | 64));
});
return sb.toString().replaceAll(" ", "" + emptyFallback).split("\n");
}
}
record Placed(long lemma, int slotKey, int[] cells) {
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
public static final char HORIZONTAL = 'h';
static final char VERTICAL = 'v';
public int arrowCol() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].c(); }
public int arrowRow() { return SwedishGenerator.IT[Slot.clueIndex(slotKey)].r(); }
public int startRow() { return SwedishGenerator.IT[cells[0]].r(); }
public int startCol() { return SwedishGenerator.IT[cells[0]].c(); }
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL };
public int arrowCol() { return Masker.IT[Slot.clueIndex(slotKey)].c(); }
public int arrowRow() { return Masker.IT[Slot.clueIndex(slotKey)].r(); }
public int startRow() { return Masker.IT[cells[0]].r(); }
public int startCol() { return Masker.IT[cells[0]].c(); }
public boolean isReversed() { return !Slotinfo.increasing(slotKey); }
public char direction() { return DIRECTION[Slot.dir(slotKey)]; }
}
@@ -214,9 +241,9 @@ public record Export() {
public record WordOut(String word, int[] cell, int startRow, int startCol, char direction, int arrowRow, int arrowCol, boolean isReversed, int complex, String[] clue) {
public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed) {
val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l));
this(Lemma.asWord(l), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed,
public WordOut(long l, int startRow, int startCol, char d, int arrowRow, int arrowCol, boolean isReversed, byte[] bytes) {
val meta = Meta.readRecord(Meta.shardKey(l), Lemma.unpackShardIndex(l));
this(Lemma.asWord(l, bytes), new int[]{ arrowRow, arrowCol, startRow, startCol }, startRow, startCol, d, arrowRow, arrowCol, isReversed,
meta.simpel(), meta.clues());
}
}
@@ -243,7 +270,7 @@ public record Export() {
return new ExportedPuzzle(grid.exportGrid(clues.c, _ -> '#', '#'), new WordOut[0], difficulty, rewards);
}
var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.walk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray(
var placed = Arrays.stream(slots).map(slot -> new Placed(slot.assign().w, slot.key(), Gridded.cellWalk((byte) slot.key(), slot.lo(), slot.hi()).toArray())).toArray(
Placed[]::new);
// 2) bounding box around all word cells + arrow cells, with 1-cell margin
@@ -252,7 +279,7 @@ public record Export() {
for (var rc : placed) {
for (var c : rc.cells) {
val it = SwedishGenerator.IT[c];
val it = Masker.IT[c];
minR = Math.min(minR, it.r());
minC = Math.min(minC, it.c());
maxR = Math.max(maxR, it.r());
@@ -270,12 +297,13 @@ public record Export() {
var gridv2 = new String[Math.max(0, maxR - minR + 1)];
for (int r = minR, i = 0; r <= maxR; r++, i++) {
var row = new StringBuilder(Math.max(0, maxC - minC + 1));
for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Grid.offset(r, c), '#'));
for (var c = minC; c <= maxC; c++) row.append(map.getOrDefault(Masker.offset(r, c), '#'));
gridv2[i] = row.toString();
}
// 5) words output with cropped coordinates
int MIN_R = minR, MIN_C = minC;
val bytes = BYTES.get();
var wordsOut = Arrays.stream(placed).map(p -> new WordOut(
p.lemma,
p.startRow() - MIN_R,
@@ -283,7 +311,7 @@ public record Export() {
p.direction(),
p.arrowRow() - MIN_R,
p.arrowCol() - MIN_C,
p.isReversed()
p.isReversed(), bytes
)).toArray(WordOut[]::new);
return new ExportedPuzzle(gridv2, wordsOut, difficulty, rewards);
}

View File

@@ -373,34 +373,6 @@ public class Main {
return null;
}
}
static Clued generateClues() {
String simple = "000 3000\n" +
" 3 \n" +
" 31 \n" +
" 3\n" +
"1 \n" +
"1 \n" +
"1 2\n" +
"1 222 3";
String sampleComplex = "1 0000\n" +
"1 \n" +
"00 01 \n" +
" 1 \n" +
" 1 \n" +
" 2 1 \n" +
" 1 \n" +
"221 22\n";
String def = " 30000\n" +
"0 001 \n" +
" 1 \n" +
" 3 \n" +
" 3 \n" +
" 32 \n" +
" 32 2\n" +
"2222 3";
return Clues.parse(sampleComplex
);
}
static Clues generateNewClues(Rng rng, Opts opts) {
var masker = new Masker(rng, new int[STACK_SIZE], Masker.Clues.createEmpty());
return masker.generateMask(opts.clueSize, opts.pop, opts.gens, opts.offspring);
@@ -415,7 +387,7 @@ public class Main {
val slotInfo = Masker.slots(mask, dict.index());
var grid = Slotinfo.grid(slotInfo);// mask.toGrid();
var filled = fillMask(rng, slotInfo, grid, (!Main.VERBOSE || multiThreaded));
var filled = fillMask(rng, slotInfo, grid);
if (!multiThreaded) {
System.out.print("\r" + Strings.padRight("", 120) + "\r");
@@ -451,7 +423,7 @@ public class Main {
System.out.println(Arrays.stream(new Clued(mask).gridToString().split("\n")).map(s -> "\"" + s + "\\n\" +").collect(Collectors.joining("\n")));
}
if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) {
return new PuzzleResult(new Clued(mask), new Gridded(grid), slotInfo, filled);
return new PuzzleResult(new Clued(mask), new Gridded(grid, mask), slotInfo, filled);
}
if (opts.verbose && filled.ok()) {

View File

@@ -2,24 +2,22 @@ package puzzle;
import lombok.AllArgsConstructor;
import lombok.val;
import puzzle.Export.Clued;
import puzzle.Export.Gridded;
import precomp.Neighbors9x8;
import precomp.Neighbors9x8.rci;
import java.sql.Array;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Objects;
import java.util.stream.IntStream;
import static java.lang.Long.*;
import static puzzle.SwedishGenerator.*;
public final class Masker {
private final Rng rng;
private final int[] stack;
private final Clues cache;
public static final rci[] IT = Neighbors9x8.IT;
private final Rng rng;
private final int[] stack;
private final Clues cache;
private final int[] activeCIdx = new int[SwedishGenerator.SIZE];
private final long[] activeSLo = new long[SwedishGenerator.SIZE];
private final long[] activeSHi = new long[SwedishGenerator.SIZE];
@@ -42,8 +40,8 @@ public final class Masker {
for (int dc1 = -2; dc1 <= 2; dc1++)
for (int dc2 = -2; dc2 <= 2; dc2++) {
val ti = IT[i];
MUTATE_RI[i][k++] = Grid.offset(SwedishGenerator.clamp(ti.r() + dr1 + dr2, 0, R - 1),
SwedishGenerator.clamp(ti.c() + dc1 + dc2, 0, C - 1));
MUTATE_RI[i][k++] = offset(clamp(ti.r() + dr1 + dr2, 0, R - 1),
clamp(ti.c() + dc1 + dc2, 0, C - 1));
}
}
}
@@ -70,6 +68,27 @@ public final class Masker {
if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN)
visitor.visit(key, rayLo, rayHi);
}
private static boolean validSlotRev(long lo, long hi, int min, int key) {
long rayLo = PATH_LO[key];
long rayHi = PATH_HI[key];
// only consider clue cells
long hitsLo = rayLo & lo;
long hitsHi = rayHi & hi;
if (hitsHi != X) return (Long.bitCount(rayHi & -(1L << 63 - numberOfLeadingZeros(hitsHi) << 1)) >= min);
else if (hitsLo != X) return (Long.bitCount(rayLo & -(1L << 63 - numberOfLeadingZeros(hitsLo) << 1)) + Long.bitCount(rayHi) >= min);
else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min);
}
private static boolean validSlot(long lo, long hi, int min, int key) {
long rayLo = PATH_LO[key];
long rayHi = PATH_HI[key];
long hitsLo = rayLo & lo;
long hitsHi = rayHi & hi;
if (hitsLo != X) return (Long.bitCount(rayLo & ((1L << numberOfTrailingZeros(hitsLo)) - 1)) >= min);
else if (hitsHi != X) return (Long.bitCount(rayLo) + Long.bitCount(rayHi & ((1L << numberOfTrailingZeros(hitsHi)) - 1)) >= min);
else return (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= min);
}
private static void processSlot(Clues c, SlotVisitor visitor, int key) {
long rayLo = PATH_LO[key];
long rayHi = PATH_HI[key];
@@ -79,10 +98,9 @@ public final class Masker {
if (hitsLo != X) {
long stop = 1L << numberOfTrailingZeros(hitsLo);
rayLo &= (stop - 1);
rayHi = 0; // any hi is beyond the stop
rayHi = 0;
} else if (hitsHi != X) {
long stop = 1L << numberOfTrailingZeros(hitsHi);
// keep all lo (lo indices are < any hi index), but cut hi below stop
rayHi &= (stop - 1);
}
if (Long.bitCount(rayLo) + Long.bitCount(rayHi) >= MIN_LEN)
@@ -116,6 +134,8 @@ public final class Masker {
for (long b = hi; b != X; b &= b - 1) cross += (count[64 | numberOfTrailingZeros(b)] - 1);
return cross * 10 + Slot.length(lo, hi);
}
public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
public static int offset(int r, int c) { return r | (c << 3); }
public long maskFitness(final Clues grid, int clueSize) {
@@ -216,7 +236,8 @@ public final class Masker {
// Connectiviteitscheck
for (int i = 0; i < numClues; i++) {
adjLo[i] = 0; adjHi[i] = 0;
adjLo[i] = 0;
adjHi[i] = 0;
}
for (int i = 0; i < numClues; i++) {
for (int j = i + 1; j < numClues; j++) {
@@ -240,22 +261,22 @@ public final class Masker {
stack[0] = 0;
int sp = 1;
while (sp > 0) {
int cur = stack[--sp];
int cur = stack[--sp];
long nLo = adjLo[cur] & ~reachedLo;
long nHi = adjHi[cur] & ~reachedHi;
while (nLo != 0) {
long lsb = nLo & -nLo;
int idx = numberOfTrailingZeros(lsb);
reachedLo |= lsb;
int idx = numberOfTrailingZeros(lsb);
reachedLo |= lsb;
stack[sp++] = idx;
nLo &= ~lsb;
nLo &= ~lsb;
}
while (nHi != 0) {
long lsb = nHi & -nHi;
int idx = 64 | numberOfTrailingZeros(lsb);
reachedHi |= lsb;
int idx = 64 | numberOfTrailingZeros(lsb);
reachedHi |= lsb;
stack[sp++] = idx;
nHi &= ~lsb;
nHi &= ~lsb;
}
}
int count = bitCount(reachedLo) + bitCount(reachedHi);
@@ -501,29 +522,13 @@ public final class Masker {
return best.grid;
}//@formatter:off
@FunctionalInterface public interface SlotVisitor { void visit(int key, long lo, long hi); }
//@formatter:on
@AllArgsConstructor
public static class Clues {
long lo, hi, vlo, vhi, rlo, rhi, xlo, xhi;
public static Clues createEmpty() { return new Clues(0, 0, 0, 0, 0, 0, 0, 0); }
public static Clued parse(String s) {
var c = createEmpty();
var lines = s.split("\n");
for (int r = 0; r < Math.min(lines.length, R); r++) {
var line = lines[r];
for (int col = 0; col < Math.min(line.length(), C); col++) {
char ch = line.charAt(col);
if (ch >= '0' && ch <= '4') {
int idx = Grid.offset(r, col);
byte dir = (byte) (ch - '0');
if ((idx & 64) == 0) c.setClueLo(1L << idx, dir);
else c.setClueHi(1L << (idx & 63), dir);
}
}
}
return new Clued(c);
}
public boolean cluelessLo(int idx) {
if (!isClueLo(idx)) return false;
clearClueLo(~(1L << idx));
@@ -536,10 +541,8 @@ public final class Masker {
}
public boolean hasRoomForClue(int key, long packed) {
if (packed == X || !notClue(packed & 0x7FL) || !notClue((packed >>> 7) & 0x7FL)) return false;
final boolean[] res = {false};
if (Slotinfo.increasing(key)) processSlot(this, (k, lo, hi) -> res[0] = true, key);
else processSlotRev(this, (k, lo, hi) -> res[0] = true, key);
return res[0];
if (Slotinfo.increasing(key)) if (!validSlot(lo, hi, MIN_LEN, key)) return false;
return validSlotRev(lo, hi, MIN_LEN, key);
}
public void setClueLo(long mask, byte idx) {
@@ -586,16 +589,18 @@ public final class Masker {
}
public Grid toGrid() { return new Grid(new byte[SwedishGenerator.SIZE], lo, hi); }
public boolean isValid(int minLen) {
class ValidationVisitor implements SlotVisitor {
boolean ok = true;
@Override
public void visit(int key, long lo, long hi) {
if (bitCount(lo) + bitCount(hi) < minLen) ok = false;
}
}
ValidationVisitor v = new ValidationVisitor();
forEachSlot(v);
return v.ok;
for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 1))) return false;
for (var l = lo & ~xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 0))) return false;
for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 2))) return false;
for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 3))) return false;
for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(numberOfTrailingZeros(l), 4))) return false;
for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1))) return false;
for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0))) return false;
for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2))) return false;
for (var h = hi & ~xhi & rhi & vhi; h != X; h &= h - 1) if (!validSlotRev(lo, hi, minLen, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 3))) return false;
for (var h = hi & xhi & ~rhi & ~vhi; h != X; h &= h - 1) if (!validSlot(lo, hi, minLen, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 4))) return false;
return true;
}
public void forEachSlot(SlotVisitor visitor) {
for (var l = lo & ~xlo & ~rlo & vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 1));
@@ -603,7 +608,7 @@ public final class Masker {
for (var l = lo & ~xlo & rlo & ~vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 2));
for (var l = lo & ~xlo & rlo & vlo; l != X; l &= l - 1) processSlotRev(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 3));
for (var l = lo & xlo & ~rlo & ~vlo; l != X; l &= l - 1) processSlot(this, visitor, Slot.packSlotKey(numberOfTrailingZeros(l), 4));
for (var h = hi & ~xhi & ~rhi & vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 1));
for (var h = hi & ~xhi & ~rhi & ~vhi; h != X; h &= h - 1) processSlot(this, visitor, Slot.packSlotKey(64 | numberOfTrailingZeros(h), 0));
for (var h = hi & ~xhi & rhi & ~vhi; h != X; h &= h - 1) processSlotRev(this, visitor, Slot.packSlotKey((64 | numberOfTrailingZeros(h)), 2));
@@ -640,11 +645,10 @@ public final class Masker {
static final int BIT_FOR_DIR = 3;
public static Slot from(int key, long lo, long hi, DictEntry entry) { return new Slot(key, lo, hi, entry); }
public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); }
public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; }
public static int dir(int key) { return key & 7; }
public IntStream walk() { return Gridded.walk((byte) key, lo, hi); }
public static boolean horiz(int d) { return (d & 1) != 0 && (d & 4) == 0; }
public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
public static int length(long lo, long hi) { return bitCount(lo) + bitCount(hi); }
public static int clueIndex(int key) { return key >>> BIT_FOR_DIR; }
public static int dir(int key) { return key & 7; }
public static boolean horiz(int d) { return (d & 1) != 0 && (d & 4) == 0; }
public static int packSlotKey(int idx, int d) { return (idx << BIT_FOR_DIR) | d; }
}
}

View File

@@ -1,16 +1,10 @@
package puzzle;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import lombok.val;
import precomp.Neighbors9x8;
import precomp.Neighbors9x8.rci;
import java.util.List;
import java.util.Locale;
import static java.lang.Long.*;
import static java.lang.Long.numberOfTrailingZeros;
import static java.nio.charset.StandardCharsets.US_ASCII;
@@ -38,9 +32,7 @@ public record SwedishGenerator() {
public static final int LOG_EVERY_MS = 200;
public static final int BAR_LEN = 22;
public static final int C = Config.PUZZLE_COLS;
public static final double CROSS_R = (C - 1) / 2.0;
public static final int R = Config.PUZZLE_ROWS;
public static final double CROSS_C = (R - 1) / 2.0;
public static final int SIZE = C * R;// ~18
public static final int SIZE_MIN_1 = SIZE - 1;// ~18
public static final double SIZED = (double) SIZE;// ~18
@@ -55,7 +47,7 @@ public record SwedishGenerator() {
public static final byte DASH = (byte) C_DASH;
public static final long RANGE_0_SIZE = (long) SIZE_MIN_1 - 0L + 1L;
public static final long RANGE_0_624 = 624L - 0L + 1L;
public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
public static boolean isLo(int n) { return (n & 64) == 0; }
interface Bit1029 {
static long[] bit1029() { return new long[2048]; }
@@ -64,10 +56,7 @@ public record SwedishGenerator() {
static void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; }
static void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); }
}
static String padRight(String s, int n) {
if (s.length() >= n) return s;
return s + " ".repeat(n - s.length());
}
@AllArgsConstructor
public static class Pick {
@@ -76,29 +65,36 @@ public record SwedishGenerator() {
public int count;
}
public static final long[] OFFSETS_D_IDX=Neighbors9x8.OFFSETS_D_IDX;
public static final rci[] IT = Neighbors9x8.IT;
//@formatter:off
public static record Dict(DictEntry[] index, int length) { }
@AllArgsConstructor @NoArgsConstructor static class Assign { long w; }
public static final class FillStats { public double simplicity; }
//@formatter:on
@AllArgsConstructor
public static class Grid {
public final byte[] g;
public long lo, hi;
}
public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) { }
public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
public static final long[] OFFSETS_D_IDX = Neighbors9x8.OFFSETS_D_IDX;
public static final long[] NBR8_PACKED_LO = Neighbors9x8.NBR8_PACKED_LO;
public static final long[] NBR8_PACKED_HI = Neighbors9x8.NBR8_PACKED_HI;
public static final long[] PATH_LO= Neighbors9x8.PATH_LO;
public static final long[] PATH_HI= Neighbors9x8.PATH_HI;
public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true);
public static final Pick PICK_NOT_DONE = new Pick(null, null, 0);
public static final long[] PATH_LO = Neighbors9x8.PATH_LO;
public static final long[] PATH_HI = Neighbors9x8.PATH_HI;
@RequiredArgsConstructor
public static final class FillStats {
public double simplicity;
}
public static final Pick PICK_DONE = null;//new Pick(null, null, 0, true);
public static record FillResult(boolean ok, long nodes, long backtracks, int lastMRV, long elapsed, FillStats stats) {
}
public static final Pick PICK_NOT_DONE = new Pick(null, null, 0);
public static final class Rng {
@Getter private int x;
private int x;
public Rng(int seed) {
var s = seed;
if (s == 0) s = 1;
@@ -114,9 +110,9 @@ public record SwedishGenerator() {
}
public byte randint2bitByte() {
int r = nextU32() & 7;
if (r < 4) return (byte) r;
if (r == 4) return (byte) 4;
return (byte) (r % 4);
//if (r < 4) return (byte) r;
return (byte) (r & 3);
}
public <T> T rand(T[] p) { return p[(int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.length /*- 0L*/ /*+ 1L*/)))]; }
public <T> T rand(List<T> p) { return p.get((int) (((nextU32() & 0xFFFFFFFFL) % ((long) p.size() /*- 0L*/ /*+ 1L*/)))); }
@@ -127,20 +123,6 @@ public record SwedishGenerator() {
public int biasedIndexPow3(int N) { return (int) (((Math.min(nextU32(), Math.min(nextU32(), nextU32())) & 0xFFFFFFFFL) * (long) N) >>> 32); }
}
@AllArgsConstructor
public static class Grid {
public final byte[] g;
public long lo, hi;
public static int offset(int r, int c) { return r | (c << 3); }
public Grid copy() {
return new Grid(g.clone(), lo, hi);
}
}
public static record DictEntry(long[] words, long[][] posBitsets, int length, int numlong) { }
public static interface Lemma {
static final long LETTER_MASK = (1L << 40) - 1; // low 40 bits
@@ -148,28 +130,20 @@ public record SwedishGenerator() {
static long from(byte[] word) { return packShiftIn(word) | ((long) (word.length - 1) << 40); }
static long pack(long w, int shardIndex) { return w | (((long) shardIndex) << 43) | ((long) length0(w)) << 40; }
/* static long pack(byte[] b) {
long w = 0;
for (var i = 0; i < b.length; i++) w |= ((long) b[i] & 31) << (i * 5);
return w;
}*/
static long packShiftIn(byte[] b) {
long w = 0;
for (int i = b.length - 1; i >= 0; i--) w = (w << 5) | ((long) b[i] & 31);
return w;
}
static public long from(String word) { return packShiftIn(word.getBytes(US_ASCII)) | ((long) (word.length() - 1) << 40); }
static byte byteAt(long word, int idx) { return (byte) ((word >>> ((long) idx * 5L)) & 0b11111L); }
static byte byteAt(long word, int idx) { return (byte) ((word >>> (idx * 5)) & 0b11111L); }
static int length0(long word) { return ((63 - numberOfLeadingZeros(word & LETTER_MASK)) / 5); }
static ThreadLocal<byte[]> BYTES = ThreadLocal.withInitial(() -> new byte[MAX_WORD_LENGTH]);
public static String asWord(long word) {
var b = BYTES.get();
public static String asWord(long word, byte[] bytes) {
int bi = 0;
for (long w = word & LETTER_MASK; w != 0; w >>>= 5) {
b[bi++] = (byte) ((w & 31) | 64); // neem laagste 5 bits
bytes[bi++] = (byte) ((w & 31) | 64); // neem laagste 5 bits
}
return new String(b, 0, bi, US_ASCII);
return new String(bytes, 0, bi, US_ASCII);
}
static int unpackIndex(long w) { return (int) (w >>> 40); }
static int unpackShardIndex(long w) { return (int) (w >>> 43); }
@@ -177,15 +151,6 @@ public record SwedishGenerator() {
static int unpackLetters(long w) { return (int) (w & LETTER_MASK); }
}
public static record Dict(DictEntry[] index, int length) { }
@AllArgsConstructor
@NoArgsConstructor
static class Assign {
long w;
}
public static record Slotinfo(int key, long lo, long hi, int score, Assign assign, DictEntry entry) {
public static int wordCount(int k, Slotinfo[] arr) {
@@ -203,34 +168,29 @@ public record SwedishGenerator() {
}
}
public static boolean isLo(int n) { return (n & 64) == 0; }
public static long patternForSlot(final long glo, final long ghi, final byte[] g, final int key, final long lo, final long hi) {
if (((lo & glo) | (hi & ghi)) == X) return 0;
if (((lo & glo) | (hi & ghi)) == X) return X;
long p = 0;
int n = 0;
int n = 0, offset, idx;
if (Slotinfo.increasing(key)) {
for (long b = lo & glo; b != X; b &= b - 1) {
int idx = numberOfTrailingZeros(b);
int i = bitCount(lo & ((1L << idx) - 1));
p |= ((long) (i * 26 + g[idx])) << (n++ << 3);
idx = numberOfTrailingZeros(b);
p |= ((long) (bitCount(lo & ((1L << idx) - 1)) * 26 + g[idx])) << (n++ << 3);
}
int offset = bitCount(lo);
offset = bitCount(lo);
for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = numberOfTrailingZeros(b);
int i = offset + bitCount(hi & ((1L << idx) - 1));
p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3);
idx = numberOfTrailingZeros(b);
p |= ((long) ((offset + bitCount(hi & ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3);
}
} else {
int offset = bitCount(hi);
offset = bitCount(hi);
for (long b = hi & ghi; b != X; b &= b - 1) {
int idx = numberOfTrailingZeros(b);
int i = bitCount(hi & ~((1L << idx) | ((1L << idx) - 1)));
p |= ((long) (i * 26 + g[64 | idx])) << (n++ << 3);
idx = numberOfTrailingZeros(b);
p |= ((long) (bitCount(hi & ~((1L << idx) | ((1L << idx) - 1))) * 26 + g[64 | idx])) << (n++ << 3);
}
for (long b = lo & glo; b != X; b &= b - 1) {
int idx = numberOfTrailingZeros(b);
int i = offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)));
p |= ((long) (i * 26 + g[idx])) << (n++ << 3);
idx = numberOfTrailingZeros(b);
p |= ((long) ((offset + bitCount(lo & ~((1L << idx) | ((1L << idx) - 1)))) * 26 + g[idx])) << (n++ << 3);
}
}
return p;
@@ -268,8 +228,7 @@ public record SwedishGenerator() {
}
public static FillResult fillMask(final Rng rng, final Slotinfo[] slots,
final Grid grid,
final boolean NO_LOG) {
final Grid grid) {
val used = new long[2048];
val bitset = new long[2500];
val g = grid.g;
@@ -283,27 +242,6 @@ public record SwedishGenerator() {
int lastMRV;
long lastLog = t0, glo = grid.lo, ghi = grid.hi;
Pick current = CARRIER;
void renderProgress() {
var now = System.currentTimeMillis();
if ((now - lastLog) < LOG_EVERY_MS) return;
lastLog = (now);
var done = 0;
for (var lemma : slots) {
if (lemma.assign.w != X) done++;
}
var pct = (TOTAL == 0) ? 100 : (int) Math.floor((done / (double) TOTAL) * 100);
var filled = Math.min(BAR_LEN, (int) Math.floor((pct / 100.0) * BAR_LEN));
var bar = "[" + "#".repeat(filled) + "-".repeat(BAR_LEN - filled) + "]";
var elapsed = String.format(Locale.ROOT, "%.1fs", (now - t0) / 1000.0);
var msg = String.format(
Locale.ROOT,
"%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s",
bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed
);
System.out.print("\r" + padRight(msg, 120));
System.out.flush();
}
boolean placeWordDec(final long lo, final long hi, final long w) {
int idx;
int bcHi = bitCount(hi);
@@ -358,7 +296,6 @@ public record SwedishGenerator() {
if (count <= 1) break;
}
}
// Re-calculate for the best slot to get actual indices
if (best == null) {
current = PICK_DONE;
return;
@@ -383,7 +320,6 @@ public record SwedishGenerator() {
}
val info = pick.indices;
lastMRV = pick.count;
if (!NO_LOG) renderProgress();
val s = pick.slot;
val inc = Slotinfo.increasing(s.key);
@@ -397,11 +333,8 @@ public record SwedishGenerator() {
var tries = Math.min(MAX_TRIES_PER_SLOT, L);
for (var t = 0; t < tries; t++) {
//var r = rng.nextFloat();
//var idxInArray = (int) (r * r * r * (L - 1));
int idxInArray = rng.biasedIndexPow3(L - 1);
var w = entry.words[idxs[idxInArray/*(int) (r * r * r * (L - 1))*/]];
var lemIdx = Lemma.unpackIndex(w);
var w = entry.words[idxs[rng.biasedIndexPow3(L - 1)]];
var lemIdx = Lemma.unpackIndex(w);
if (Bit1029.get(used, lemIdx)) continue;
low = glo;
top = ghi;
@@ -427,8 +360,7 @@ public record SwedishGenerator() {
var tries = Math.min(MAX_TRIES_PER_SLOT, N);
for (var t = 0; t < tries; t++) {
// double r = rng.nextFloat();
var w = entry.words[rng.biasedIndexPow3(N - 1)/*(int) (r * r * r * (N - 1))*/];
var w = entry.words[rng.biasedIndexPow3(N - 1)];
var lemIdx = Lemma.unpackIndex(w);
if (Bit1029.get(used, lemIdx)) continue;
low = glo;
@@ -454,11 +386,8 @@ public record SwedishGenerator() {
}
}
// initial render (same feel)
var solver = new Solver();
if (!NO_LOG) solver.renderProgress();
var ok = solver.backtrack(0);
// final progress line
var ok = solver.backtrack(0);
grid.lo = solver.glo;
grid.hi = solver.ghi;