introduce bitloops
This commit is contained in:
@@ -7,9 +7,15 @@ import lombok.val;
|
||||
import puzzle.Export.Gridded.Replacar.Cell;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.Masker.Clues;
|
||||
import puzzle.SwedishGenerator.Dict;
|
||||
import puzzle.SwedishGenerator.DictEntry;
|
||||
import puzzle.SwedishGenerator.FillResult;
|
||||
import puzzle.SwedishGenerator.Grid;
|
||||
import puzzle.SwedishGenerator.Slotinfo;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -191,15 +197,6 @@ public record Export() {
|
||||
}
|
||||
}
|
||||
|
||||
interface Bit1029 {
|
||||
|
||||
static long[] bit1029() { return new long[2048]; }
|
||||
static int wordIndex(int bitIndex) { return bitIndex >> 6; }
|
||||
static boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; }
|
||||
static void set(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] |= 1L << bitIndex; }
|
||||
static void clear(long[] bits, int bitIndex) { bits[wordIndex(bitIndex)] &= ~(1L << bitIndex); }
|
||||
}
|
||||
|
||||
record Placed(long lemma, int slotKey, int[] cells) {
|
||||
|
||||
static final char[] DIRECTION = { Placed.VERTICAL, Placed.HORIZONTAL, Placed.VERTICAL, Placed.HORIZONTAL };
|
||||
@@ -286,6 +283,51 @@ public record Export() {
|
||||
}
|
||||
}
|
||||
|
||||
interface Dicts {
|
||||
|
||||
static Dict loadDict(String wordsPath) {
|
||||
try {
|
||||
var map = new LongArrayList(100_000);
|
||||
Files.lines(Path.of(wordsPath), StandardCharsets.UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add));
|
||||
return makeDict(map.toArray());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to load dictionary from " + wordsPath, e);
|
||||
}
|
||||
}
|
||||
static Dict makeDict(long[] wordz) {
|
||||
var index = new DictEntryDTO[SwedishGenerator.MAX_WORD_LENGTH_PLUS_ONE];
|
||||
Arrays.setAll(index, i -> new DictEntryDTO(i));
|
||||
for (var lemma : wordz) {
|
||||
var L = Lemma.length(lemma);
|
||||
|
||||
var entry = index[L];
|
||||
var idx = entry.words().size();
|
||||
entry.words().add(lemma);
|
||||
for (var i = 0; i < L; i++) entry.pos()[i][Lemma.byteAt(lemma, i) - 1].add(idx);
|
||||
}
|
||||
for (int i = SwedishGenerator.MIN_LEN; i < index.length; i++) if (index[i].words().size() <= 0) throw new RuntimeException("No words for length " + i);
|
||||
return new Dict(Arrays.stream(index).map(i -> {
|
||||
var words = i.words().toArray();
|
||||
int numWords = words.length;
|
||||
int numLongs = (numWords + 63) >>> 6;
|
||||
var bitsets = new long[i.pos().length * 26][numLongs];
|
||||
for (int p = 0; p < i.pos().length; p++) {
|
||||
for (int l = 0; l < 26; l++) {
|
||||
var list = i.pos()[p][l];
|
||||
var bs = bitsets[p * 26 + l];
|
||||
for (int k = 0; k < list.size(); k++) {
|
||||
int wordIdx = list.data()[k];
|
||||
bs[wordIdx >>> 6] |= (1L << (wordIdx & 63));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DictEntry(words, bitsets, words.length, (words.length + 63) >>> 6);
|
||||
}).toArray(DictEntry[]::new),
|
||||
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
|
||||
}
|
||||
}
|
||||
|
||||
record DictEntryDTO(LongArrayList words, IntListDTO[][] pos) {
|
||||
|
||||
public DictEntryDTO(int L) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
import static puzzle.CsvIndexService.SC;
|
||||
import static puzzle.Export.*;
|
||||
import static puzzle.SwedishGenerator.*;
|
||||
import static puzzle.SwedishGenerator.Dict.loadDict;
|
||||
import static puzzle.Export.Dicts.loadDict;
|
||||
|
||||
public class Main {
|
||||
|
||||
@@ -392,9 +392,21 @@ public class Main {
|
||||
val slotInfo = Masker.scoreSlots(new int[slots.length], slots);
|
||||
var grid = mask.toGrid();
|
||||
var filled = fillMask(rng, slotInfo, grid, multiThreaded);
|
||||
|
||||
if (!multiThreaded) {
|
||||
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
||||
System.out.flush();
|
||||
}
|
||||
// print a final progress line
|
||||
if (Main.VERBOSE && !multiThreaded) {
|
||||
System.out.printf(Locale.ROOT,
|
||||
"[######################] %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %.1fs%n",
|
||||
Slotinfo.wordCount(0, slotInfo), slotInfo.length, filled.nodes(), filled.backtracks(), filled.stats().lastMRV(), filled.stats().seconds()
|
||||
);
|
||||
}
|
||||
|
||||
TOTAL_NODES.addAndGet(filled.stats().nodes);
|
||||
TOTAL_BACKTRACKS.addAndGet(filled.stats().backtracks);
|
||||
TOTAL_NODES.addAndGet(filled.nodes());
|
||||
TOTAL_BACKTRACKS.addAndGet(filled.backtracks());
|
||||
if (filled.ok()) {
|
||||
val simpel = FillResult.calcSimpel(slotInfo);
|
||||
TOTAL_SUCCESS.incrementAndGet();
|
||||
@@ -404,12 +416,12 @@ public class Main {
|
||||
var name = Thread.currentThread().getName();
|
||||
var status = filled.ok() ? "SUCCESS" : "FAILED";
|
||||
var simplicity = String.format(Locale.ROOT, "%.2f", filled.stats().simplicity);
|
||||
var nps = (int) (filled.stats().nodes / Math.max(0.001, filled.stats().seconds));
|
||||
var nps = (int) (filled.nodes() / Math.max(0.001, filled.stats().seconds));
|
||||
var totalTime = (System.currentTimeMillis() - t0) / 1000.0;
|
||||
|
||||
System.out.printf(Locale.ROOT,
|
||||
"[ATTEMPT] thread=%s | status=%s | nodes=%d | backtracks=%d | nps=%d | simplicity=%s | time=%.1fs%n",
|
||||
name, status, filled.stats().nodes, filled.stats().backtracks, nps, simplicity, totalTime
|
||||
name, status, filled.nodes(), filled.backtracks(), nps, simplicity, totalTime
|
||||
);
|
||||
|
||||
if (filled.ok() && (opts.minSimplicity <= 0 || filled.stats().simplicity >= opts.minSimplicity)) {
|
||||
|
||||
@@ -9,15 +9,6 @@ import lombok.experimental.Delegate;
|
||||
import lombok.val;
|
||||
import precomp.Neighbors9x8;
|
||||
import precomp.Neighbors9x8.rci;
|
||||
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.Arrays;
|
||||
import java.util.Locale;
|
||||
import static java.lang.Long.*;
|
||||
import static java.lang.Long.numberOfTrailingZeros;
|
||||
@@ -66,6 +57,18 @@ public class SwedishGenerator {
|
||||
public static final long RANGE_0_624 = 624L - 0L + 1L;
|
||||
public static final int CLUE_INDEX_MAX_SIZE = (288 | 3) + 1;
|
||||
public static int clamp(int x, int a, int b) { return Math.max(a, Math.min(b, x)); }
|
||||
interface Bit1029 {
|
||||
|
||||
static long[] bit1029() { return new long[2048]; }
|
||||
private static int wordIndex(int bitIndex) { return bitIndex >> 6; }
|
||||
static boolean get(long[] bits, int bitIndex) { return (bits[wordIndex(bitIndex)] & 1L << bitIndex) != 0L; }
|
||||
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 {
|
||||
|
||||
@@ -88,24 +91,20 @@ public class SwedishGenerator {
|
||||
@Accessors(fluent = true)
|
||||
public static final class FillStats {
|
||||
|
||||
final public long nodes;
|
||||
final public long backtracks;
|
||||
final public double seconds;
|
||||
final public int lastMRV;
|
||||
public double simplicity;
|
||||
}
|
||||
|
||||
public static record FillResult(boolean ok,
|
||||
|
||||
@Delegate FillStats stats) {
|
||||
public static record FillResult(boolean ok, long nodes, long backtracks, @Delegate FillStats stats) {
|
||||
|
||||
static public long calcSimpel(Slotinfo[] slots) {
|
||||
int k = 0;
|
||||
long simpel = 0L;
|
||||
for (var n = 1; n < slots.length; n++) {
|
||||
if (slots[n].assign().w != X) {
|
||||
if (slots[n].assign.w != X) {
|
||||
k++;
|
||||
simpel += Lemma.simpel(slots[n].assign().w);
|
||||
simpel += Lemma.simpel(slots[n].assign.w);
|
||||
}
|
||||
}
|
||||
simpel = k == 0 ? 0 : simpel / k;
|
||||
@@ -176,52 +175,7 @@ public class SwedishGenerator {
|
||||
static int unpackLetters(long w) { return (int) (w & LETTER_MASK); }
|
||||
}
|
||||
|
||||
public static record Dict(
|
||||
DictEntry[] index,
|
||||
int length) {
|
||||
|
||||
public Dict(long[] wordz) {
|
||||
var index = new DictEntryDTO[MAX_WORD_LENGTH_PLUS_ONE];
|
||||
Arrays.setAll(index, i -> new DictEntryDTO(i));
|
||||
for (var lemma : wordz) {
|
||||
var L = Lemma.length(lemma);
|
||||
|
||||
var entry = index[L];
|
||||
var idx = entry.words().size();
|
||||
entry.words().add(lemma);
|
||||
for (var i = 0; i < L; i++) entry.pos()[i][Lemma.byteAt(lemma, i) - 1].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(Arrays.stream(index).map(i -> {
|
||||
var words = i.words().toArray();
|
||||
int numWords = words.length;
|
||||
int numLongs = (numWords + 63) >>> 6;
|
||||
var bitsets = new long[i.pos().length * 26][numLongs];
|
||||
for (int p = 0; p < i.pos().length; p++) {
|
||||
for (int l = 0; l < 26; l++) {
|
||||
var list = i.pos()[p][l];
|
||||
var bs = bitsets[p * 26 + l];
|
||||
for (int k = 0; k < list.size(); k++) {
|
||||
int wordIdx = list.data()[k];
|
||||
bs[wordIdx >>> 6] |= (1L << (wordIdx & 63));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DictEntry(words, bitsets, words.length, (words.length + 63) >>> 6);
|
||||
}).toArray(DictEntry[]::new),
|
||||
Arrays.stream(index).mapToInt(i -> i.words().size()).sum());
|
||||
}
|
||||
static Dict loadDict(String wordsPath) {
|
||||
try {
|
||||
var map = new LongArrayList(100_000);
|
||||
Files.lines(Path.of(wordsPath), StandardCharsets.UTF_8).forEach(line -> CsvIndexService.lineToLemma(line, map::add));
|
||||
return new Dict(map.toArray());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException("Failed to load dictionary from " + wordsPath, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static record Dict(DictEntry[] index, int length) { }
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -391,7 +345,7 @@ public class SwedishGenerator {
|
||||
"%s %d/%d slots | nodes=%d | backtracks=%d | mrv=%d | %s",
|
||||
bar, done, TOTAL, nodes, backtracks, lastMRV, elapsed
|
||||
);
|
||||
System.out.print("\r" + Strings.padRight(msg, 120));
|
||||
System.out.print("\r" + padRight(msg, 120));
|
||||
System.out.flush();
|
||||
}
|
||||
boolean placeWord(final int key, final long lo, final long hi, final long w) {
|
||||
@@ -548,22 +502,7 @@ public class SwedishGenerator {
|
||||
// final progress line
|
||||
grid.lo = solver.glo;
|
||||
grid.hi = solver.ghi;
|
||||
var res = new FillResult(ok,
|
||||
new FillStats(solver.nodes, solver.backtracks, (System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
|
||||
if (!multiThreaded) {
|
||||
System.out.print("\r" + Strings.padRight("", 120) + "\r");
|
||||
System.out.flush();
|
||||
}
|
||||
// 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",
|
||||
Slotinfo.wordCount(0, slots), TOTAL, res.nodes(), res.backtracks(), res.lastMRV(), res.seconds()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
return new FillResult(ok, solver.nodes, solver.backtracks, new FillStats((System.currentTimeMillis() - t0) / 1000.0, solver.lastMRV));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class ExportFormatTest {
|
||||
|
||||
assertTrue(placeWord(grid.grid(), grid.grid().g, key, lo, 0L, TEST));
|
||||
|
||||
var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0));
|
||||
var fillResult = new FillResult(true, 0, 0, new FillStats(0, 0));
|
||||
var puzzleResult = new PuzzleResult(new Clued(clues), grid, new Slotinfo[]{
|
||||
new Slotinfo(key, lo, 0L, 0, new Assign(TEST), null)
|
||||
}, fillResult);
|
||||
@@ -109,7 +109,7 @@ public class ExportFormatTest {
|
||||
void testExportFormatEmpty() {
|
||||
var grid = SwedishGeneratorTest.createEmpty();
|
||||
val clues = Clues.createEmpty();
|
||||
var fillResult = new FillResult(true, new FillStats(0, 0, 0, 0));
|
||||
var fillResult = new FillResult(true, 0, 0, new FillStats(0, 0));
|
||||
var puzzleResult = new PuzzleResult(new Clued(clues), new Gridded(grid), new Slotinfo[0], fillResult);
|
||||
|
||||
var exported = puzzleResult.exportFormatFromFilled(1, new Rewards(0, 0, 0));
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.ClueAt;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Dicts;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
import puzzle.Export.PuzzleResult;
|
||||
@@ -42,7 +43,7 @@ public class MainTest {
|
||||
this.tries = 1;
|
||||
this.verbose = false;
|
||||
}};
|
||||
static final Dict dict = Dict.loadDict(opts.wordsPath);
|
||||
static final Dict dict = Dicts.loadDict(opts.wordsPath);
|
||||
@Test
|
||||
void testExtractSlots() {
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.val;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import puzzle.Export.Clued;
|
||||
import puzzle.Export.Dicts;
|
||||
import puzzle.Export.Gridded;
|
||||
import puzzle.Export.IntListDTO;
|
||||
import puzzle.Export.LetterVisit.LetterAt;
|
||||
@@ -219,7 +220,7 @@ public class SwedishGeneratorTest {
|
||||
assertEquals(5, Lemma.length(l1));
|
||||
assertEquals(LETTER_A, Lemma.byteAt(l1, 0));
|
||||
|
||||
var dict = new Dict(new long[]{ l1, l2, l2a, l4a, l6a, l7a, l8a });
|
||||
var dict = Dicts.makeDict(new long[]{ l1, l2, l2a, l4a, l6a, l7a, l8a });
|
||||
|
||||
assertEquals(1, dict.index()[3].words().length);
|
||||
assertEquals(1, dict.index()[5].words().length);
|
||||
@@ -275,7 +276,7 @@ public class SwedishGeneratorTest {
|
||||
|
||||
@Test
|
||||
void testCandidateInfoForPattern() {
|
||||
var dict = new Dict(WORDS2);
|
||||
var dict = Dicts.makeDict(WORDS2);
|
||||
|
||||
// Pattern "APP--" for length 5
|
||||
var info = candidateInfoForPattern(Context.get().bitset(), packPattern("APP"), dict.index()[5].posBitsets(), dict.index()[5].numlong());
|
||||
@@ -289,7 +290,7 @@ public class SwedishGeneratorTest {
|
||||
// This should detect a slot starting at 0,1 with length 2 (0,1 and 0,2)
|
||||
var clues = Masker.Clues.createEmpty();
|
||||
clues.setClueLo(IDX_0_0.lo, CLUE_RIGHT);
|
||||
var dict = new Dict(WORDS2);
|
||||
var dict = Dicts.makeDict(WORDS2);
|
||||
var slots = Masker.extractSlots(clues, dict.index());
|
||||
assertEquals(1, slots.length);
|
||||
var s = slots[0];
|
||||
@@ -407,7 +408,7 @@ public class SwedishGeneratorTest {
|
||||
val counts = new byte[SIZE];
|
||||
counts[1] = 2;
|
||||
counts[2] = 3;
|
||||
var dict = new Dict(WORDS);
|
||||
var dict = Dicts.makeDict(WORDS);
|
||||
var entry5 = dict.index()[5];
|
||||
// cross = (counts[1]-1) + (counts[2]-1) = 1 + 2 = 3
|
||||
// score = 3 * 10 + len(2) = 32
|
||||
|
||||
Reference in New Issue
Block a user