Gather data
This commit is contained in:
@@ -3,9 +3,10 @@ package puzzle;
|
||||
import com.google.gson.Gson;
|
||||
import lombok.Getter;
|
||||
import lombok.val;
|
||||
import puzzle.ExportFormat.Bit;
|
||||
import puzzle.ExportFormat.Bit1029;
|
||||
import puzzle.ExportFormat.Gridded;
|
||||
import puzzle.Export.Bit;
|
||||
import puzzle.Export.Bit1029;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.Strings;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -16,6 +17,7 @@ import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@@ -30,18 +32,6 @@ public record SwedishGenerator() {
|
||||
|
||||
record CandidateInfo(int[] indices, int count) { }
|
||||
|
||||
record nbrs_8(int r, int c) { }
|
||||
|
||||
record nbrs_16(int r, int c, int dr, int dc, int d, byte dbyte) {
|
||||
|
||||
public nbrs_16(int r, int c, int dr, int dc, int d) {
|
||||
this(r, c, dr, dc, d, (byte) (48 + d));
|
||||
}
|
||||
}
|
||||
|
||||
record rci(int r, int c, int i, long n1, long n2, int nbrCount, long n8_1, long n8_2) {
|
||||
}
|
||||
|
||||
static final int BAR_LEN = 22;
|
||||
static final int C = Config.PUZZLE_COLS;
|
||||
static final double CROSS_Y = (C - 1) / 2.0;
|
||||
@@ -61,8 +51,12 @@ public record SwedishGenerator() {
|
||||
static boolean isLetter(byte b) { return (b & 64) != 0; }
|
||||
static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
|
||||
|
||||
public SwedishGenerator() { }
|
||||
public SwedishGenerator(int[] buff) { this(); }
|
||||
record nbrs_8(int r, int c) { }
|
||||
|
||||
record nbrs_16(int r, int c, int dr, int dc, int d, byte dbyte) {
|
||||
|
||||
public nbrs_16(int r, int c, int dr, int dc, int d) { this(r, c, dr, dc, d, (byte) (48 + d)); }
|
||||
}
|
||||
|
||||
// Directions for '1'..'6'
|
||||
static final nbrs_16[] OFFSETS = new nbrs_16[]{
|
||||
@@ -91,6 +85,8 @@ public record SwedishGenerator() {
|
||||
new nbrs_8(0, 1)
|
||||
};
|
||||
|
||||
record rci(int r, int c, int i, long n1, long n2, int nbrCount, long n8_1, long n8_2) { }
|
||||
|
||||
static final rci[] IT = new rci[SIZE];
|
||||
static {
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
@@ -120,6 +116,35 @@ public record SwedishGenerator() {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class FillStats {
|
||||
|
||||
public long nodes;
|
||||
public long backtracks;
|
||||
public double seconds;
|
||||
public int lastMRV;
|
||||
}
|
||||
|
||||
record Pick(Slot slot, CandidateInfo info, boolean done) { }
|
||||
|
||||
static final Pick PICK_DONE = new Pick(null, null, true);
|
||||
static final Pick PICK_NOT_DONE = new Pick(null, null, false);
|
||||
|
||||
public static record FillResult(boolean ok,
|
||||
Gridded grid,
|
||||
HashMap<Integer, Lemma> clueMap,
|
||||
FillStats stats,
|
||||
double simplicity) {
|
||||
|
||||
public FillResult(boolean ok, Gridded grid, HashMap<Integer, Lemma> assigned, FillStats stats) {
|
||||
double totalSimplicity = 0;
|
||||
if (ok) {
|
||||
for (var w : assigned.values()) totalSimplicity += w.simpel;
|
||||
totalSimplicity = assigned.isEmpty() ? 0 : totalSimplicity / assigned.size();
|
||||
}
|
||||
this(ok, grid, assigned, stats, totalSimplicity);
|
||||
}
|
||||
}
|
||||
|
||||
static record Context(int[] covH,
|
||||
int[] covV,
|
||||
int[] cellCount,
|
||||
@@ -208,25 +233,15 @@ public record SwedishGenerator() {
|
||||
for (int i = 0; i < SIZE; i++) if (g[i] == b.g[i]) same++;
|
||||
return same / SIZED;
|
||||
}
|
||||
int clueCount() {
|
||||
return Long.bitCount(bo[0]) + Long.bitCount(bo[1]);
|
||||
int clueCount() { return Long.bitCount(bo[0]) + Long.bitCount(bo[1]); }
|
||||
void forEachSetBit71() { forEachSetBit71(null); }
|
||||
void forEachSetBit71(IntConsumer consumer) {
|
||||
for (var lo = bo[0]; lo != 0; lo &= lo - 1) consumer.accept(Long.numberOfTrailingZeros(lo));
|
||||
for (var hi = bo[1]; hi != 0; hi &= hi - 1) consumer.accept(64 + Long.numberOfTrailingZeros(hi));
|
||||
}
|
||||
void forEachSetBit71(java.util.function.IntConsumer consumer) {
|
||||
long lo = bo[0], hi = bo[1];
|
||||
// low 64 bits
|
||||
while (lo != 0L) {
|
||||
int bit = Long.numberOfTrailingZeros(lo);
|
||||
consumer.accept(bit); // 0..63
|
||||
lo &= (lo - 1); // clear lowest set bit
|
||||
}
|
||||
|
||||
// high 7 bits (positions 64..70)
|
||||
// hi &= 0x7FL;
|
||||
while (hi != 0L) {
|
||||
int bit = Long.numberOfTrailingZeros(hi);
|
||||
consumer.accept(64 + bit); // 64..70
|
||||
hi &= (hi - 1L);
|
||||
}
|
||||
void forEachSlot(SlotVisitor visitor) {
|
||||
for (var lo = bo[0]; lo != 0; lo &= lo - 1) processSlot(this, visitor, Long.numberOfTrailingZeros(lo));
|
||||
for (var hi = bo[1]; hi != 0; hi &= hi - 1) processSlot(this, visitor, 64 + Long.numberOfTrailingZeros(hi));
|
||||
}
|
||||
}
|
||||
static Grid makeEmptyGrid() { return new Grid(new byte[SIZE], new long[2]); }
|
||||
@@ -351,73 +366,21 @@ public record SwedishGenerator() {
|
||||
return k;
|
||||
}
|
||||
|
||||
CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) {
|
||||
var pattern = ctx.pattern;
|
||||
var listBuffer = ctx.intListBuffer;
|
||||
var listCount = 0;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var ch = pattern[i];
|
||||
if (isLetter(ch)) {
|
||||
listBuffer[listCount++] = entry.pos[i][ch - 'A'];
|
||||
}
|
||||
}
|
||||
|
||||
if (listCount == 0) {
|
||||
return new CandidateInfo(null, entry.words.size());
|
||||
}
|
||||
|
||||
// Sort constraints by size to optimize intersection
|
||||
for (int i = 0; i < listCount - 1; i++) {
|
||||
for (int j = i + 1; j < listCount; j++) {
|
||||
if (listBuffer[j].size() < listBuffer[i].size()) {
|
||||
var tmp = listBuffer[i];
|
||||
listBuffer[i] = listBuffer[j];
|
||||
listBuffer[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cur = listBuffer[0].data();
|
||||
var curLen = listBuffer[0].size();
|
||||
if (listCount == 1) return new CandidateInfo(cur, curLen);
|
||||
|
||||
int[] b1 = ctx.inter1;
|
||||
int[] b2 = ctx.inter2;
|
||||
int[] in = cur;
|
||||
int[] out = b1;
|
||||
|
||||
for (var k = 1; k < listCount; k++) {
|
||||
var nxt = listBuffer[k];
|
||||
curLen = intersectSorted(in, curLen, nxt.data(), nxt.size(), out);
|
||||
in = out;
|
||||
out = (out == b1) ? b2 : b1;
|
||||
if (curLen == 0) break;
|
||||
}
|
||||
|
||||
return new CandidateInfo(in, curLen);
|
||||
}
|
||||
static record Slot(int key, long packedPos) {
|
||||
|
||||
static Slot from(int key, long packedPos, int len) {
|
||||
return new Slot(key, packedPos | ((long) len << 56));
|
||||
}
|
||||
|
||||
public int len() { return (int) (packedPos >>> 56); }
|
||||
public int clueR() { return Grid.r((key >>> 4)); }
|
||||
public int clueIndex() { return key >>> 4; }
|
||||
public int clueC() { return Grid.c((key >>> 4)); }
|
||||
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 int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
|
||||
static Slot from(int key, long packedPos, int len) { return new Slot(key, packedPos | ((long) len << 56)); }
|
||||
public int len() { return (int) (packedPos >>> 56); }
|
||||
public int clueR() { return Grid.r((key >>> 4)); }
|
||||
public int clueIndex() { return key >>> 4; }
|
||||
public int clueC() { return Grid.c((key >>> 4)); }
|
||||
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 int offset(long packedPos, int i) { return (int) ((packedPos >> (i * 7)) & 127); }
|
||||
}
|
||||
static void undoPlace(Grid grid, Slot s, int mask) {
|
||||
for (int i = 0, len = s.len(); i < len; i++) {
|
||||
if ((mask & (1L << i)) != 0) {
|
||||
grid.clear(s.pos(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0, len = s.len(); i < len; i++) if ((mask & (1L << i)) != 0) grid.clear(s.pos(i));
|
||||
}
|
||||
@FunctionalInterface
|
||||
interface SlotVisitor {
|
||||
@@ -425,22 +388,7 @@ public record SwedishGenerator() {
|
||||
void visit(int key, long packedPos, int len);
|
||||
}
|
||||
|
||||
void forEachSlot(Grid grid, SlotVisitor visitor) {
|
||||
long lo = grid.bo[0], hi = grid.bo[1];
|
||||
int idx;
|
||||
while (lo != 0) {
|
||||
idx = Long.numberOfTrailingZeros(lo);
|
||||
lo &= (lo - 1);
|
||||
processSlot(grid, visitor, idx);
|
||||
}
|
||||
while (hi != 0) {
|
||||
idx = 64 + Long.numberOfTrailingZeros(hi);
|
||||
hi &= (hi - 1);
|
||||
processSlot(grid, visitor, idx);
|
||||
}
|
||||
}
|
||||
|
||||
private void processSlot(Grid grid, SlotVisitor visitor, int idx) {
|
||||
private static void processSlot(Grid grid, SlotVisitor visitor, int idx) {
|
||||
var d = grid.digitAt(idx);
|
||||
var nbrs16 = OFFSETS[d];
|
||||
int r = Grid.r(idx), c = Grid.c(idx), rr = r + nbrs16.r, cc = c + nbrs16.c;
|
||||
@@ -459,9 +407,9 @@ public record SwedishGenerator() {
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Slot> extractSlots(Grid grid) {
|
||||
static ArrayList<Slot> extractSlots(Grid grid) {
|
||||
var slots = new ArrayList<Slot>(32);
|
||||
forEachSlot(grid, (key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len)));
|
||||
grid.forEachSlot((key, packedPos, len) -> slots.add(Slot.from(key, packedPos, len)));
|
||||
return slots;
|
||||
}
|
||||
boolean hasRoomForClue(Grid grid, int idx, nbrs_16 nbrs16) {
|
||||
@@ -642,15 +590,13 @@ public record SwedishGenerator() {
|
||||
var nx = Math.cos(theta);
|
||||
var ny = Math.sin(theta);
|
||||
|
||||
for (var rci : IT) {
|
||||
out.setAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i));
|
||||
}
|
||||
for (var rci : IT) out.setAt(rci.i, ((rci.r - CROSS_X) * nx + (rci.c - CROSS_Y) * ny >= 0) ? a.byteAt(rci.i) : b.byteAt(rci.i));
|
||||
for (var rci : IT) if (out.isDigitAt(rci.i) && !hasRoomForClue(out, rci.i, OFFSETS[out.digitAt(rci.i)])) out.clearClue(rci.i);
|
||||
return out;
|
||||
}
|
||||
|
||||
Grid hillclimb(Rng rng, Grid start, int[] lenCounts, int limit) {
|
||||
var best = start;//.deepCopyGrid();
|
||||
var best = start;
|
||||
var bestF = maskFitness(best, lenCounts);
|
||||
var fails = 0;
|
||||
|
||||
@@ -668,7 +614,7 @@ public record SwedishGenerator() {
|
||||
return best;
|
||||
}
|
||||
|
||||
public Grid generateMask(Rng rng, int[] lenCounts, int popSize, int gens, boolean verbose) {
|
||||
public Grid generateMask(Rng rng, int[] lenCounts, int popSize, int gens) {
|
||||
class GridAndFit {
|
||||
|
||||
Grid grid;
|
||||
@@ -679,7 +625,7 @@ public record SwedishGenerator() {
|
||||
return this.fite;
|
||||
}
|
||||
}
|
||||
if (verbose) System.out.println("generateMask init pop: " + popSize);
|
||||
if (Main.VERBOSE) System.out.println("generateMask init pop: " + popSize);
|
||||
var pop = new ArrayList<GridAndFit>();
|
||||
for (var i = 0; i < popSize; i++) {
|
||||
pop.add(new GridAndFit(hillclimb(rng, randomMask(rng), lenCounts, 180)));
|
||||
@@ -714,7 +660,7 @@ public record SwedishGenerator() {
|
||||
}
|
||||
pop = next;
|
||||
|
||||
if (verbose && gen % 10 == 0) {
|
||||
if (Main.VERBOSE && gen % 10 == 0) {
|
||||
var bestF = pop.get(0).fit();
|
||||
System.out.println(" gen " + gen + "/" + gens + " bestFitness=" + bestF);
|
||||
}
|
||||
@@ -723,38 +669,6 @@ public record SwedishGenerator() {
|
||||
pop.sort(Comparator.comparingLong(GridAndFit::fit));
|
||||
return pop.get(0).grid;
|
||||
}
|
||||
|
||||
// ---------------- Fill (CSP) ----------------
|
||||
|
||||
public static final class FillStats {
|
||||
|
||||
public long nodes;
|
||||
public long backtracks;
|
||||
public double seconds;
|
||||
public int lastMRV;
|
||||
}
|
||||
|
||||
record Pick(Slot slot, CandidateInfo info, boolean done) { }
|
||||
|
||||
static final Pick PICK_DONE = new Pick(null, null, true);
|
||||
static final Pick PICK_NOT_DONE = new Pick(null, null, false);
|
||||
|
||||
public static record FillResult(boolean ok,
|
||||
Gridded grid,
|
||||
HashMap<Integer, Lemma> clueMap,
|
||||
FillStats stats,
|
||||
double simplicity) {
|
||||
|
||||
public FillResult(boolean ok, Gridded grid, HashMap<Integer, Lemma> assigned, FillStats stats) {
|
||||
double totalSimplicity = 0;
|
||||
if (ok) {
|
||||
for (var w : assigned.values()) totalSimplicity += w.simpel;
|
||||
totalSimplicity = assigned.isEmpty() ? 0 : totalSimplicity / assigned.size();
|
||||
}
|
||||
this(ok, grid, assigned, stats, totalSimplicity);
|
||||
}
|
||||
}
|
||||
|
||||
static void patternForSlot(Grid grid, Slot s, byte[] pat) {
|
||||
for (int i = 0, len = s.len(); i < len; i++) {
|
||||
var ch = grid.byteAt(s.pos(i));
|
||||
@@ -789,116 +703,199 @@ public record SwedishGenerator() {
|
||||
undoBuffer[offset] = mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
public FillResult fillMask(Rng rng, Grid mask, DictEntry[] dictIndex,
|
||||
int logEveryMs, int timeLimitMs, boolean verbose) {
|
||||
boolean multiThreaded = Thread.currentThread().getName().contains("pool");
|
||||
var grid = mask.deepCopyGrid();
|
||||
var slots = extractSlots(grid);
|
||||
static CandidateInfo candidateInfoForPattern(Context ctx, DictEntry entry, int len) {
|
||||
var pattern = ctx.pattern;
|
||||
var listBuffer = ctx.intListBuffer;
|
||||
var listCount = 0;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var ch = pattern[i];
|
||||
if (isLetter(ch)) {
|
||||
listBuffer[listCount++] = entry.pos[i][ch - 'A'];
|
||||
}
|
||||
}
|
||||
|
||||
var used = new Bit1029();
|
||||
var assigned = new HashMap<Integer, Lemma>();
|
||||
if (listCount == 0) {
|
||||
return new CandidateInfo(null, entry.words.size());
|
||||
}
|
||||
|
||||
var ctx = CTX.get();
|
||||
var count = ctx.cellCount;
|
||||
Arrays.fill(count, 0, SIZE, 0);
|
||||
for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++;
|
||||
|
||||
var t0 = System.currentTimeMillis();
|
||||
final var lastLog = new AtomicLong(t0);
|
||||
|
||||
var stats = new FillStats();
|
||||
final var TOTAL = slots.size();
|
||||
|
||||
Runnable renderProgress = () -> {
|
||||
if (!verbose || multiThreaded) return;
|
||||
var now = System.currentTimeMillis();
|
||||
if ((now - lastLog.get()) < logEveryMs) return;
|
||||
lastLog.set(now);
|
||||
|
||||
var done = assigned.size();
|
||||
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, stats.nodes, stats.backtracks, stats.lastMRV, elapsed
|
||||
);
|
||||
System.out.print("\r" + padRight(msg, 120));
|
||||
System.out.flush();
|
||||
};
|
||||
|
||||
Supplier<Pick> chooseMRV = () -> {
|
||||
Slot best = null;
|
||||
CandidateInfo bestInfo = null;
|
||||
int bestSlot = -1;
|
||||
for (var s : slots) {
|
||||
if (assigned.containsKey(s.key())) continue;
|
||||
var entry = dictIndex[s.len()];
|
||||
if (entry == null) return PICK_NOT_DONE;
|
||||
patternForSlot(grid, s, ctx.pattern);
|
||||
var info = candidateInfoForPattern(ctx, entry, s.len());
|
||||
|
||||
if (info.count == 0) return PICK_NOT_DONE;
|
||||
var slotScore = -1;
|
||||
if (best == null
|
||||
|| info.count < bestInfo.count
|
||||
|| (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) {
|
||||
best = s;
|
||||
bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s);
|
||||
if (info.indices != null && (info.indices == ctx.inter1 || info.indices == ctx.inter2)) {
|
||||
bestInfo = new CandidateInfo(Arrays.copyOf(info.indices, info.count), info.count);
|
||||
} else {
|
||||
bestInfo = info;
|
||||
}
|
||||
if (info.count <= 1) break;
|
||||
// Sort constraints by size to optimize intersection
|
||||
for (int i = 0; i < listCount - 1; i++) {
|
||||
for (int j = i + 1; j < listCount; j++) {
|
||||
if (listBuffer[j].size() < listBuffer[i].size()) {
|
||||
var tmp = listBuffer[i];
|
||||
listBuffer[i] = listBuffer[j];
|
||||
listBuffer[j] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (best == null) {
|
||||
return PICK_DONE;
|
||||
} else {
|
||||
return new Pick(best, bestInfo, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class Solver {
|
||||
var cur = listBuffer[0].data();
|
||||
var curLen = listBuffer[0].size();
|
||||
if (listCount == 1) return new CandidateInfo(cur, curLen);
|
||||
|
||||
int[] b1 = ctx.inter1;
|
||||
int[] b2 = ctx.inter2;
|
||||
int[] in = cur;
|
||||
int[] out = b1;
|
||||
|
||||
for (var k = 1; k < listCount; k++) {
|
||||
var nxt = listBuffer[k];
|
||||
curLen = intersectSorted(in, curLen, nxt.data(), nxt.size(), out);
|
||||
in = out;
|
||||
out = (out == b1) ? b2 : b1;
|
||||
if (curLen == 0) break;
|
||||
}
|
||||
|
||||
return new CandidateInfo(in, curLen);
|
||||
}
|
||||
record CSP(Rng rng) {
|
||||
|
||||
public FillResult fillMask(Grid mask, DictEntry[] dictIndex,
|
||||
int logEveryMs, int timeLimitMs) {
|
||||
boolean multiThreaded = Thread.currentThread().getName().contains("pool");
|
||||
var grid = mask.deepCopyGrid();
|
||||
var slots = extractSlots(grid);
|
||||
|
||||
boolean backtrack(int depth) {
|
||||
if (Thread.currentThread().isInterrupted()) return false;
|
||||
stats.nodes++;
|
||||
var used = new Bit1029();
|
||||
var assigned = new HashMap<Integer, Lemma>();
|
||||
|
||||
var ctx = CTX.get();
|
||||
var count = ctx.cellCount;
|
||||
Arrays.fill(count, 0, SIZE, 0);
|
||||
for (var s : slots) for (int i = 0, len = s.len(); i < len; i++) count[s.pos(i)]++;
|
||||
|
||||
var t0 = System.currentTimeMillis();
|
||||
final var lastLog = new AtomicLong(t0);
|
||||
|
||||
var stats = new FillStats();
|
||||
final var TOTAL = slots.size();
|
||||
|
||||
Runnable renderProgress = () -> {
|
||||
if (!Main.VERBOSE || multiThreaded) return;
|
||||
var now = System.currentTimeMillis();
|
||||
if ((now - lastLog.get()) < logEveryMs) return;
|
||||
lastLog.set(now);
|
||||
|
||||
if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false;
|
||||
var done = assigned.size();
|
||||
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 pick = chooseMRV.get();
|
||||
if (pick.done) return true;
|
||||
if (pick.slot == null) {
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
val info = pick.info;
|
||||
stats.lastMRV = info.count;
|
||||
renderProgress.run();
|
||||
|
||||
var s = pick.slot;
|
||||
var k = s.key();
|
||||
int patLen = s.len();
|
||||
var entry = dictIndex[patLen];
|
||||
var pat = new byte[patLen];
|
||||
patternForSlot(grid, s, pat);
|
||||
if (info.indices != null && info.indices.length > 0) {
|
||||
var idxs = info.indices;
|
||||
var L = idxs.length;
|
||||
var tries = Math.min(MAX_TRIES_PER_SLOT, L);
|
||||
var msg = String.format(
|
||||
Locale.ROOT,
|
||||
"%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s",
|
||||
bar, done, TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, elapsed
|
||||
);
|
||||
System.out.print("\r" + Strings.padRight(msg, 120));
|
||||
System.out.flush();
|
||||
};
|
||||
|
||||
Supplier<Pick> chooseMRV = () -> {
|
||||
Slot best = null;
|
||||
CandidateInfo bestInfo = null;
|
||||
int bestSlot = -1;
|
||||
for (var s : slots) {
|
||||
if (assigned.containsKey(s.key())) continue;
|
||||
var entry = dictIndex[s.len()];
|
||||
if (entry == null) return PICK_NOT_DONE;
|
||||
patternForSlot(grid, s, ctx.pattern);
|
||||
var info = SwedishGenerator.candidateInfoForPattern(ctx, entry, s.len());
|
||||
|
||||
if (info.count == 0) return PICK_NOT_DONE;
|
||||
var slotScore = -1;
|
||||
if (best == null
|
||||
|| info.count < bestInfo.count
|
||||
|| (info.count == bestInfo.count && (slotScore = slotScore(count, s)) > bestSlot)) {
|
||||
best = s;
|
||||
bestSlot = (slotScore != -1) ? slotScore : slotScore(count, s);
|
||||
if (info.indices != null && (info.indices == ctx.inter1 || info.indices == ctx.inter2)) {
|
||||
bestInfo = new CandidateInfo(Arrays.copyOf(info.indices, info.count), info.count);
|
||||
} else {
|
||||
bestInfo = info;
|
||||
}
|
||||
if (info.count <= 1) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (best == null) {
|
||||
return PICK_DONE;
|
||||
} else {
|
||||
return new Pick(best, bestInfo, false);
|
||||
}
|
||||
};
|
||||
|
||||
class Solver {
|
||||
|
||||
boolean backtrack(int depth) {
|
||||
if (Thread.currentThread().isInterrupted()) return false;
|
||||
stats.nodes++;
|
||||
|
||||
if (timeLimitMs > 0 && (System.currentTimeMillis() - t0) > timeLimitMs) return false;
|
||||
|
||||
var pick = chooseMRV.get();
|
||||
if (pick.done) return true;
|
||||
if (pick.slot == null) {
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
val info = pick.info;
|
||||
stats.lastMRV = info.count;
|
||||
renderProgress.run();
|
||||
|
||||
var s = pick.slot;
|
||||
var k = s.key();
|
||||
int patLen = s.len();
|
||||
var entry = dictIndex[patLen];
|
||||
var pat = new byte[patLen];
|
||||
patternForSlot(grid, s, pat);
|
||||
if (info.indices != null && info.indices.length > 0) {
|
||||
var idxs = info.indices;
|
||||
var L = idxs.length;
|
||||
var tries = Math.min(MAX_TRIES_PER_SLOT, L);
|
||||
|
||||
for (var t = 0; t < tries; t++) {
|
||||
double r = rng.nextFloat();
|
||||
int idxInArray = (int) (r * r * r * L);
|
||||
var idx = idxs[idxInArray];
|
||||
var w = entry.words.get(idx);
|
||||
|
||||
if (used.get(w.index())) continue;
|
||||
|
||||
boolean match = true;
|
||||
for (var i = 0; i < patLen; i++) {
|
||||
if (pat[i] != DASH && pat[i] != w.byteAt(i)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue;
|
||||
|
||||
used.set(w.index());
|
||||
assigned.put(k, w);
|
||||
|
||||
if (backtrack(depth + 1)) return true;
|
||||
|
||||
assigned.remove(k);
|
||||
used.clear(w.index);
|
||||
undoPlace(grid, s, ctx.undo[depth]);
|
||||
}
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var N = entry.words.size();
|
||||
if (N == 0) {
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tries = Math.min(MAX_TRIES_PER_SLOT, N);
|
||||
for (var t = 0; t < tries; t++) {
|
||||
double r = rng.nextFloat();
|
||||
int idxInArray = (int) (r * r * r * L);
|
||||
var idx = idxs[idxInArray];
|
||||
var w = entry.words.get(idx);
|
||||
int idxInArray = (int) (r * r * r * N);
|
||||
var w = entry.words.get(idxInArray);
|
||||
|
||||
if (used.get(w.index())) continue;
|
||||
|
||||
@@ -909,7 +906,6 @@ public record SwedishGenerator() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue;
|
||||
|
||||
used.set(w.index());
|
||||
@@ -921,76 +917,36 @@ public record SwedishGenerator() {
|
||||
used.clear(w.index);
|
||||
undoPlace(grid, s, ctx.undo[depth]);
|
||||
}
|
||||
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var N = entry.words.size();
|
||||
if (N == 0) {
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
|
||||
var tries = Math.min(MAX_TRIES_PER_SLOT, N);
|
||||
for (var t = 0; t < tries; t++) {
|
||||
double r = rng.nextFloat();
|
||||
int idxInArray = (int) (r * r * r * N);
|
||||
var w = entry.words.get(idxInArray);
|
||||
|
||||
if (used.get(w.index())) continue;
|
||||
|
||||
boolean match = true;
|
||||
for (var i = 0; i < patLen; i++) {
|
||||
if (pat[i] != DASH && pat[i] != w.byteAt(i)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match || !placeWord(grid, s, w, ctx.undo, depth)) continue;
|
||||
|
||||
used.set(w.index());
|
||||
assigned.put(k, w);
|
||||
|
||||
if (backtrack(depth + 1)) return true;
|
||||
|
||||
assigned.remove(k);
|
||||
used.clear(w.index);
|
||||
undoPlace(grid, s, ctx.undo[depth]);
|
||||
}
|
||||
|
||||
stats.backtracks++;
|
||||
return false;
|
||||
}
|
||||
|
||||
// initial render (same feel)
|
||||
renderProgress.run();
|
||||
var ok = new Solver().backtrack(0);
|
||||
// final progress line
|
||||
if (!multiThreaded) {
|
||||
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
|
||||
var res = new FillResult(ok, new Gridded(grid), assigned, stats);
|
||||
|
||||
// print a final progress line
|
||||
if (Main.VERBOSE && !multiThreaded) {
|
||||
System.out.println(
|
||||
String.format(Locale.ROOT,
|
||||
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
|
||||
assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// initial render (same feel)
|
||||
renderProgress.run();
|
||||
var ok = new Solver().backtrack(0);
|
||||
// final progress line
|
||||
if (!multiThreaded) {
|
||||
System.out.print("\r" + padRight("", 120) + "\r");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
stats.seconds = (System.currentTimeMillis() - t0) / 1000.0;
|
||||
var res = new FillResult(ok, new Gridded(grid), assigned, stats);
|
||||
|
||||
// print a final progress line
|
||||
if (verbose && !multiThreaded) {
|
||||
System.out.println(
|
||||
String.format(Locale.ROOT,
|
||||
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs",
|
||||
assigned.size(), TOTAL, stats.nodes, stats.backtracks, stats.lastMRV, stats.seconds
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static String padRight(String s, int n) {
|
||||
if (s.length() >= n) return s;
|
||||
return s + " ".repeat(n - s.length());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user