diff --git a/src/main/java/cryptator/cmd/CryptaBiConsumer.java b/src/main/java/cryptator/cmd/CryptaBiConsumer.java index 64d24f0..12e0ec4 100644 --- a/src/main/java/cryptator/cmd/CryptaBiConsumer.java +++ b/src/main/java/cryptator/cmd/CryptaBiConsumer.java @@ -19,6 +19,7 @@ import cryptator.specs.ICryptaEvaluation; import cryptator.specs.ICryptaNode; import cryptator.specs.ICryptaSolution; +import cryptator.specs.ICryptaSolutionStore; import cryptator.tree.CryptaEvaluation; import cryptator.tree.CryptaEvaluationException; import cryptator.tree.GraphvizExport; @@ -27,11 +28,11 @@ import guru.nidi.graphviz.engine.Graphviz; import guru.nidi.graphviz.model.Graph; -public class CryptaBiConsumer implements BiConsumer { +public class CryptaBiConsumer implements BiConsumer, ICryptaSolutionStore { private final Logger logger; - private long solutionCount; + private int solutionCount; private Optional lastSolution; @@ -46,7 +47,8 @@ public CryptaBiConsumer(final Logger logger) { internal = new SolutionCounter(); } - public final long getSolutionCount() { + @Override + public final int getSolutionCount() { return solutionCount; } @@ -54,10 +56,6 @@ public final Optional getLastSolution() { return lastSolution; } - public final Optional getUniqueSolution() { - return solutionCount <= 1 ? lastSolution : Optional.empty(); - } - public final int getErrorCount() { return errorCount; } @@ -94,8 +92,8 @@ private class SolutionCounter implements BiConsumer consumer) throws CryptaModelException { + public long generate(final BiConsumer consumer) throws CryptaModelException { final CryptaGenModel gen = buildModel(); clog.logOnModel(gen); @@ -119,6 +119,7 @@ public void generate(final BiConsumer consumer) th parallelSolve(gen, cons, nthreads); } clog.logOnSolver(gen); + return gen.getModel().getSolver().getSolutionCount(); } // FIXME are consumers thread-safe ? they are used in parallel ! @@ -167,13 +168,17 @@ public void accept(final ICryptaNode t) { try { final CryptaBiConsumer collect = buildBiConsumer(); solver.solve(t, config, collect); - Optional solution = collect.getUniqueSolution(); - if (solution.isPresent()) { - internal.accept(t, solution.get()); + if (collect.getErrorCount() == 0) { + final Optional solution = collect.getUniqueSolution(); + if (solution.isPresent()) { + internal.accept(t, solution.get()); + } + } else { + logger.log(Level.WARNING, "Solve the candidate cryptarithm [ERROR]"); } } catch (CryptaModelException | CryptaSolverException e) { errorCount.incrementAndGet(); - logger.log(Level.WARNING, "Fail to solve the cryptarithm", e); + logger.log(Level.WARNING, "Solve the candidate cryptarithm [FAIL]", e); } } } diff --git a/src/main/java/cryptator/specs/ICryptaGenerator.java b/src/main/java/cryptator/specs/ICryptaGenerator.java index 3935607..3c5697b 100644 --- a/src/main/java/cryptator/specs/ICryptaGenerator.java +++ b/src/main/java/cryptator/specs/ICryptaGenerator.java @@ -23,8 +23,9 @@ public interface ICryptaGenerator { * * @param consumer the consumer that handles the generated cryptarithm along * with its solution. + * @return the number of candidate cryptarithm * @throws CryptaModelException if there was an error during the generation. */ - void generate(BiConsumer consumer) throws CryptaModelException; + long generate(BiConsumer consumer) throws CryptaModelException; } diff --git a/src/main/java/cryptator/specs/ICryptaSolutionStore.java b/src/main/java/cryptator/specs/ICryptaSolutionStore.java new file mode 100644 index 0000000..d1ae5d5 --- /dev/null +++ b/src/main/java/cryptator/specs/ICryptaSolutionStore.java @@ -0,0 +1,23 @@ +/** + * This file is part of cryptator, https://github.com/arnaud-m/cryptator + * + * Copyright (c) 2022, Université Côte d'Azur. All rights reserved. + * + * Licensed under the BSD 3-clause license. + * See LICENSE file in the project root for full license information. + */ +package cryptator.specs; + +import java.util.Optional; + +public interface ICryptaSolutionStore { + + int getSolutionCount(); + + Optional getLastSolution(); + + default Optional getUniqueSolution() { + return getSolutionCount() <= 1 ? getLastSolution() : Optional.empty(); + } + +} diff --git a/src/test/java/cryptator/GenerateTest.java b/src/test/java/cryptator/GenerateTest.java index 4fcd298..b643199 100644 --- a/src/test/java/cryptator/GenerateTest.java +++ b/src/test/java/cryptator/GenerateTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; +import java.util.OptionalInt; import org.junit.BeforeClass; import org.junit.Test; @@ -28,44 +29,59 @@ public static void configureTestLoggers() { JULogUtil.configureTestLoggers(); } - public long testGenerate(final WordArray wordArray, final boolean lightModel, final boolean lightPropagation) + private void testGenerate(final int expectedSolCount, final OptionalInt expectedCandCount, + final WordArray wordArray, final boolean lightModel, final boolean lightPropagation) throws CryptaModelException { final CryptagenConfig config = new CryptagenConfig(); config.setLightModel(lightModel); config.setLightPropagation(lightPropagation); final CryptaListGenerator gen = new CryptaListGenerator(wordArray, config, Cryptagen.LOGGER); CryptaBiConsumer cons = new CryptaBiConsumer(Cryptagen.LOGGER); + cons.withSolutionLog(); cons.withSolutionCheck(config.getArithmeticBase()); - gen.generate(cons); assertEquals(0, cons.getErrorCount()); - return cons.getSolutionCount(); + long actualCandCount = gen.generate(cons); + if (expectedCandCount.isPresent()) { + assertEquals(expectedCandCount.getAsInt(), actualCandCount); + } + assertEquals(expectedSolCount, cons.getSolutionCount()); } - public void testGenerate(final int expectedSolCount, final WordArray wordArray) throws CryptaModelException { - assertEquals(expectedSolCount, testGenerate(wordArray, false, false)); - assertEquals(expectedSolCount, testGenerate(wordArray, false, true)); - assertEquals(expectedSolCount, testGenerate(wordArray, true, false)); - assertEquals(expectedSolCount, testGenerate(wordArray, true, true)); + private void testGenerate(final int expectedSolCount, final WordArray wordArray) throws CryptaModelException { + testGenerate(expectedSolCount, OptionalInt.empty(), wordArray); } - public void testGenerate(final int expectedSolCount, final String rightMember, final String... words) - throws CryptaModelException { - testGenerate(expectedSolCount, new WordArray(Arrays.asList(words), rightMember)); + private void testGenerate(final int expectedSolCount, final OptionalInt expectedCandCount, + final WordArray wordArray) throws CryptaModelException { + testGenerate(expectedSolCount, expectedCandCount, wordArray, false, false); + testGenerate(expectedSolCount, expectedCandCount, wordArray, false, true); + testGenerate(expectedSolCount, expectedCandCount, wordArray, true, false); + testGenerate(expectedSolCount, expectedCandCount, wordArray, true, true); } @Test public void testSendMoreMoney() throws CryptaModelException { - testGenerate(1, null, "send", "more", "money"); + final WordArray words = new WordArray(Arrays.asList("send", "more", "money"), null); + testGenerate(1, OptionalInt.of(1), words); + } + + @Test + public void testSendMuchMoreMoney() throws CryptaModelException { + WordArray words = new WordArray(Arrays.asList("send", "much", "more", "money"), null); + testGenerate(1, OptionalInt.of(6), words, false, true); + } @Test public void testPlanets1() throws CryptaModelException { - testGenerate(2, null, "venus", "earth", "uranus", "saturn"); + WordArray words = new WordArray(Arrays.asList("venus", "earth", "uranus", "saturn"), null); + testGenerate(2, words); } @Test public void testPlanets2() throws CryptaModelException { - testGenerate(1, "planets", "venus", "earth", "uranus", "saturn"); + WordArray words = new WordArray(Arrays.asList("venus", "earth", "uranus", "saturn"), "planets"); + testGenerate(1, words); } @Test