From 5bba12caf84fe670c06923c3ae38917655787c12 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 5 Jan 2026 23:43:44 +0100 Subject: [PATCH] Gather data --- pom.xml | 5 ++ src/main/java/puzzle/Main.java | 71 +++++++++++++++------- src/main/java/puzzle/SwedishGenerator.java | 42 +++++++------ 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/pom.xml b/pom.xml index d1934b9..55c38ee 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,11 @@ + + com.google.code.gson + gson + 2.13.2 + org.postgresql postgresql diff --git a/src/main/java/puzzle/Main.java b/src/main/java/puzzle/Main.java index 8f6f35e..3d72c34 100644 --- a/src/main/java/puzzle/Main.java +++ b/src/main/java/puzzle/Main.java @@ -137,7 +137,6 @@ public class Main { System.out.printf(Locale.ROOT, " %-14s: %s%n", "wordsPath", o.wordsPath); System.out.printf(Locale.ROOT, " %-14s: %.2f%n", "minSimplicity", o.minSimplicity); System.out.printf(Locale.ROOT, " %-14s: %d%n", "threads", o.threads); - System.out.printf(Locale.ROOT, " %-14s: %d%n", "maxTries", o.tries); } private static String fmtPoint(int r, int c) { return String.format(Locale.ROOT, "(%d,%d)", r, c); } @@ -181,8 +180,7 @@ public class Main { Defaults: --pop 18 - --gens 600 - --tries = threads + --gens 500 --words nl_score_hints.csv --min-simplicity 0 (no limit) --threads %d @@ -245,33 +243,62 @@ public class Main { section("Search"); + var deadline = System.currentTimeMillis() + 40_000; + var fillTimeout = 20_000; + if (opts.threads > 1) { info("mode : multi-threaded (" + opts.threads + ")"); - var executor = Executors.newFixedThreadPool(opts.threads); + var executor = Executors.newFixedThreadPool(opts.threads); + var completionService = new ExecutorCompletionService(executor); + int submitted = 0; + try { - var tasks = new ArrayList>(); - for (var i = 1; i <= opts.tries; i++) { - final var attempt = i; - tasks.add(() -> { + // Keep at least some tasks in flight + for (int i = 0; i < opts.threads; i++) { + final int attempt = ++submitted; + completionService.submit(() -> { var threadRng = new Rng(opts.seed + attempt); var mask = generateMask(threadRng, dict.lenCounts(), opts.pop, opts.gens, false); - var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, 30000, false); + var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, fillTimeout, false); if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) { - info("status : SOLVED"); - info("foundAtTry : " + attempt); return new PuzzleResult(dict, mask, filled); } - throw new RuntimeException("No solution in try " + attempt); + return null; }); } - return executor.invokeAny(tasks); + + while (System.currentTimeMillis() < deadline) { + var future = completionService.poll(deadline - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + if (future == null) break; + + var result = future.get(); + if (result != null) { + info("status : SOLVED"); + return result; + } + + // Submit another task if we still have time + if (System.currentTimeMillis() < deadline) { + final int attempt = ++submitted; + completionService.submit(() -> { + var threadRng = new Rng(opts.seed + attempt); + var mask = generateMask(threadRng, dict.lenCounts(), opts.pop, opts.gens, false); + var filled = fillMask(threadRng, mask, dict.index(), dict.words(), 200, fillTimeout, false); + + if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) { + return new PuzzleResult(dict, mask, filled); + } + return null; + }); + } + } + warn("status : UNSOLVED (timeout)"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); warn("status : INTERRUPTED"); } catch (ExecutionException e) { - // all failed - warn("status : UNSOLVED"); + warn("status : ERROR (" + e.getMessage() + ")"); } finally { executor.shutdownNow(); } @@ -279,13 +306,15 @@ public class Main { } else { info("mode : single-threaded"); - var rng = new Rng(opts.seed); + var rng = new Rng(opts.seed); + int attempt = 0; - for (var attempt = 1; attempt <= opts.tries; attempt++) { - info("try : " + attempt + "/" + opts.tries); + while (System.currentTimeMillis() < deadline) { + attempt++; + info("try : " + attempt + " (remaining: " + (deadline - System.currentTimeMillis()) / 1000 + "s)"); var mask = generateMask(rng, dict.lenCounts(), opts.pop, opts.gens, true); - var filled = fillMask(rng, mask, dict.index(), dict.words(), 200, 30000, true); + var filled = fillMask(rng, mask, dict.index(), dict.words(), 200, fillTimeout, true); if (filled.ok && (opts.minSimplicity <= 0 || filled.simplicity >= opts.minSimplicity)) { info("status : SOLVED"); @@ -301,7 +330,7 @@ public class Main { } } - info("status : UNSOLVED"); + info("status : UNSOLVED (timeout)"); return null; } } @@ -397,7 +426,7 @@ public class Main { info("Rebuilding index from: " + PUZZLE_DIR); - List records = new ArrayList<>(); + var records = new ArrayList(); try (var stream = Files.list(PUZZLE_DIR)) { stream.filter(p -> p.toString().endsWith(".json") && !p.getFileName().toString().equals("index.json")) .sorted(Comparator.comparing(Path::getFileName).reversed()) diff --git a/src/main/java/puzzle/SwedishGenerator.java b/src/main/java/puzzle/SwedishGenerator.java index a62166c..6157d55 100644 --- a/src/main/java/puzzle/SwedishGenerator.java +++ b/src/main/java/puzzle/SwedishGenerator.java @@ -5,7 +5,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; -import java.util.concurrent.*; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -132,7 +132,7 @@ public class SwedishGenerator { int[] data() { return a; } // note: may have extra capacity } - static record DictEntry(ArrayList words, IntList[][] pos) { + static record DictEntry(ArrayList words, IntList[][] pos) { public DictEntry(int L) { this(new ArrayList<>(), new IntList[L][26]); @@ -142,14 +142,14 @@ public class SwedishGenerator { } } - static record WordDifficulty(String word, int difficulty, int simpel, int score, int cross, ArrayList clue) { + static record Lemma(String word, int difficulty, int simpel, int score, int cross, ArrayList clue) { - public WordDifficulty(String word, int simpel, int score, String clue) { - var difficulty1 = 0 + ((8 - word.length()) * 30) + ((10 - score) * 15); - var crossScore = ThemePoolBuilderLength.crossabilityScore(word); - var list = new ArrayList(10); + public Lemma(String word, int simpel, int score, String clue) { + var complex = 0 + ((8 - word.length()) * 30) + ((10 - score) * 15); + var crossScore = ThemePoolBuilderLength.crossabilityScore(word); + var list = new ArrayList(10); list.add(clue); - this(word, difficulty1, simpel, score, (crossScore * 7) + ((score) * 30) + ((word.length()) * 15), list); + this(word, complex, simpel, score, (crossScore * 7) + ((score) * 30) + ((word.length()) * 15), list); // Prioritize simple words (high lScore) and long words. // lScore (1-10) adds up to 1000 points (weight 100). @@ -161,9 +161,10 @@ public class SwedishGenerator { // Length impact: up to 8 * 10 = 80 // Score impact: up to 10 * 15 = 150 } + char charAt(int idx) { return word.charAt(idx); } } - public static record Dict(Map words, + public static record Dict(Map words, HashMap index, HashMap lenCounts) { } static Dict loadWords(String wordsPath) { @@ -171,10 +172,11 @@ public class SwedishGenerator { try { raw = Files.readString(Path.of(wordsPath), StandardCharsets.UTF_8); } catch (IOException e) { + e.printStackTrace(); raw = "WOORD,level_1_to_10,hint\nEU,2,hint\nUUR,2,hint\nAUTO,2,hint\nBOOM,2,hint\nHUIS,2,hint\nKAT,2,hint\nZEE,2,hint\nRODE,2,hint\nDRAAD,2,hint\nKENNIS,2,hint\nNETWERK,2,hint\nPAKTE,2,hint\n"; } - var map = new HashMap(); + var map = new HashMap(); boolean first = true; for (var line : raw.split("\\R")) { if (line.isBlank()) continue; @@ -200,7 +202,7 @@ public class SwedishGenerator { if (map.containsKey(s)) { map.get(s).clue.add(rawClue); } else { - map.put(s, new WordDifficulty(s, simpel, score, rawClue)); + map.put(s, new Lemma(s, simpel, score, rawClue)); } } } @@ -223,7 +225,7 @@ public class SwedishGenerator { } var idx = entry.words.size(); - entry.words.add(w.word); + entry.words.add(w); for (var i = 0; i < L; i++) { var letter = w.word.charAt(i) - 'A'; @@ -618,7 +620,7 @@ public class SwedishGenerator { return cross * 10 + s.len; } - static Undo placeWord(char[][] grid, Slot s, String w) { + static Undo placeWord(char[][] grid, Slot s, Lemma w) { var urs = new int[s.len]; var ucs = new int[s.len]; var up = new char[s.len]; @@ -648,7 +650,7 @@ public class SwedishGenerator { } static FillResult fillMask(Rng rng, char[][] mask, HashMap dictIndex, - Map llmScores, + Map llmScores, int logEveryMs, int timeLimitMs, boolean verbose) { var grid = deepCopyGrid(mask); @@ -757,9 +759,9 @@ public class SwedishGenerator { var entry = dictIndex.get(s.len); var pat = patternForSlot(grid, s); - java.util.function.Function tryWord = (String w) -> { + Predicate tryWord = (Lemma w) -> { if (w == null) return false; - if (used.contains(w)) return false; + if (used.contains(w.word())) return false; for (var i = 0; i < pat.length; i++) { if (pat[i] != 0 && pat[i] != w.charAt(i)) return false; @@ -768,8 +770,8 @@ public class SwedishGenerator { var undo = placeWord(grid, s, w); if (undo == null) return false; - used.add(w); - assigned.put(k, w); + used.add(w.word()); + assigned.put(k, w.word()); if (backtrack()) return true; @@ -792,7 +794,7 @@ public class SwedishGenerator { int idxInArray = (int) (r * r * r * L); var idx = idxs[idxInArray]; var w = entry.words.get(idx); - if (tryWord.apply(w)) return true; + if (tryWord.test(w)) return true; } stats.backtracks++; return false; @@ -809,7 +811,7 @@ public class SwedishGenerator { double r = rng.nextFloat(); int idxInArray = (int) (r * r * r * N); var w = entry.words.get(idxInArray); - if (tryWord.apply(w)) return true; + if (tryWord.test(w)) return true; } stats.backtracks++;