Skip to content

Commit e350fed

Browse files
committed
Comments, javadoc and refactoring of the recent optimizations
1 parent d8525a8 commit e350fed

File tree

5 files changed

+62
-21
lines changed

5 files changed

+62
-21
lines changed

src/main/java/zebra4j/AbstractPuzzleGenerator.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,19 @@ public P generate() {
8787
protected abstract P toPuzzle(List<Fact> facts);
8888

8989
/**
90-
* @param puzzle
91-
* @return number of unique solutions of the given puzzle
90+
* If the puzzle has a unique solution
91+
*
92+
* @param puzzle required
93+
* @return false, if the puzzle has no or multiple solutions
9294
*/
9395
protected boolean uniqueSolution(P puzzle) {
9496
return countSolutions(puzzle) == 1;
9597
}
9698

99+
/**
100+
* @param puzzle required
101+
* @return number of unique solutions of the given puzzle
102+
*/
97103
protected abstract int countSolutions(P puzzle);
98104

99105
/**
@@ -109,6 +115,17 @@ protected boolean rejectFact(Fact fact) {
109115
return false;
110116
}
111117

118+
/**
119+
* Reduce the initial list of all compatible facts to the facts to use in the
120+
* generated puzzle
121+
*
122+
* <p>
123+
* Implementation typically override either this method or
124+
* {@link #uniqueSolution} with a more efficient solution.
125+
*
126+
* @param facts initial list of facts with which the puzzle has a unique
127+
* solution
128+
*/
112129
protected void removeFacts(List<Fact> facts) {
113130
for (int i = 0; i < facts.size(); ++i) {
114131
List<Fact> factsCopy = new ArrayList<>(facts);

src/main/java/zebra4j/PuzzleSolver.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.chocosolver.solver.Settings;
3737
import org.chocosolver.solver.Solution;
3838
import org.chocosolver.solver.Solver;
39+
import org.chocosolver.solver.variables.IntVar;
3940

4041
import lombok.AllArgsConstructor;
4142
import lombok.Getter;
@@ -59,10 +60,15 @@ public class PuzzleSolver {
5960
private Settings chocoSettings;
6061

6162
/**
62-
* @param zebraModel
63-
* @return a list of choco-solver solutions; useful to count solutions
63+
* Solve the puzzle returning a lazy stream to all solutions in a raw form,
64+
* close to the underlying Choco solver model
65+
*
66+
* @return a list of raw solutions - map from {@link Attribute} to person ID;
67+
* useful when just counting solutions
6468
*/
65-
Stream<Map<Attribute, Integer>> solveChoco(ZebraModel zebraModel) {
69+
Stream<Map<Attribute, Integer>> solveChoco() {
70+
ZebraModel zebraModel = toModel();
71+
log.debug("Solving puzzle {}", puzzle);
6672
for (Fact fact : puzzle.getFacts()) {
6773
fact.postTo(zebraModel);
6874
}
@@ -78,27 +84,38 @@ public boolean tryAdvance(Consumer<? super Map<Attribute, Integer>> action) {
7884
return false;
7985
}
8086
solution.record();
81-
action.accept(zebraModel.getVariableMap().entrySet().stream()
87+
// We extract the values of only the relevant variables. Returning a copy of the
88+
// complete solution is not efficient because the solution contains much more
89+
// internal variables and data.
90+
// For question puzzles, this can be further improved by extracting only the
91+
// attributes for towards and about, but the optimization is currently not worth
92+
// the added complexity.
93+
Set<Entry<Attribute, IntVar>> variables = zebraModel.getVariableMap().entrySet();
94+
action.accept(variables.stream()
8295
.collect(Collectors.toMap(e -> e.getKey(), e -> solution.getIntVal(e.getValue()))));
8396
return true;
8497
}
8598
};
8699
return StreamSupport.stream(spliterator, false);
87100
}
88101

102+
/**
103+
* Solves the puzzles eagerly.
104+
*
105+
* @return a list of all distinct solution.
106+
*/
89107
public List<PuzzleSolution> solve() {
90-
log.debug("Solving puzzle {}", puzzle);
91108
List<PuzzleSolution> zebraSolutions = solveToStream().distinct().collect(Collectors.toList());
92109
log.trace("Found {} distinct solutions", zebraSolutions.size());
93110
return zebraSolutions;
94111
}
95112

113+
/**
114+
* @return a lazy stream of all solutions with possible duplicates
115+
*/
96116
public Stream<PuzzleSolution> solveToStream() {
97-
ZebraModel zebraModel = toModel();
98-
Stream<Map<Attribute, Integer>> solutions = solveChoco(zebraModel);
99-
// 16% of runtime is here
100-
Stream<PuzzleSolution> stream = solutions.map(choco -> fromChocoSolution(choco));
101-
return stream;
117+
// 16% of the typical CPU cost of this method is in the map.
118+
return solveChoco().map(this::fromChocoSolution);
102119
}
103120

104121
private PuzzleSolution fromChocoSolution(Map<Attribute, Integer> choco) {
@@ -114,7 +131,7 @@ private PuzzleSolution fromChocoSolution(Map<Attribute, Integer> choco) {
114131
return builder.build();
115132
}
116133

117-
ZebraModel toModel() {
134+
private ZebraModel toModel() {
118135
ZebraModel model = new ZebraModel(chocoSettings);
119136
for (Entry<AttributeType, Set<Attribute>> entry : puzzle.getAttributeSets().entrySet()) {
120137
entry.getKey().addToModel(model, entry.getValue(), puzzle.numPeople());

src/main/java/zebra4j/Question.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
* The question is towards the person, identified by the attribute in
4141
* {@link #getTowards()} and is about the attribute of type {@link #getAbout()}.
4242
*/
43+
/**
44+
* @author mnozhchev
45+
*
46+
*/
4347
@Value
4448
public class Question {
4549
public static Question NAME_OF_CRIMINAL = new Question(Criminal.YES, PersonName.TYPE);
@@ -112,10 +116,17 @@ public Optional<Attribute> answer(PuzzleSolution solution) {
112116
return solution.findPerson(towards).flatMap(person -> Optional.ofNullable(person.findAttribute(about)));
113117
}
114118

115-
public Optional<Attribute> answer(Map<Attribute, Integer> chocoSolution, Puzzle puzzle) {
116-
int personId = chocoSolution.get(towards);
119+
/**
120+
* Answers this question about the given solution, if applicable
121+
*
122+
* @param rawSolution solution, as in {@link PuzzleSolver#solveChoco()}
123+
* @param puzzle the puzzle that the solution solves, used for metadata
124+
* @return same as {@link #answer(PuzzleSolution)}
125+
*/
126+
public Optional<Attribute> answer(Map<Attribute, Integer> rawSolution, Puzzle puzzle) {
127+
int personId = rawSolution.get(towards);
117128
Set<Attribute> attributes = puzzle.getAttributeSets().get(about);
118-
return attributes.stream().filter(attr -> chocoSolution.get(attr) == personId).findAny();
129+
return attributes.stream().filter(attr -> rawSolution.get(attr) == personId).findAny();
119130
}
120131

121132
public boolean appliesTo(Puzzle puzzle) {

src/main/java/zebra4j/QuestionPuzzleSolver.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ public Set<Attribute> solve() {
5656
}
5757

5858
public Stream<Attribute> solveToStream() {
59-
ZebraModel model = solver.toModel();
60-
return solver.solveChoco(model)
59+
return solver.solveChoco()
6160
.flatMap(solution -> toStream(qPuzzle.getQuestion().answer(solution, qPuzzle.getPuzzle())));
6261
}
6362

src/test/java/zebra4j/PerfIT.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
import static org.junit.Assert.assertEquals;
2525

26-
import java.util.Locale;
27-
2826
import org.junit.Test;
2927

3028
import zebra4j.Cli.QSample;
@@ -34,7 +32,6 @@ public class PerfIT {
3432
@Test
3533
public void testQuestion() throws Exception {
3634
QSample sample = Cli.sampleQuestionPuzzle(1, 5);
37-
Cli.printQuestionPuzzle(sample, Locale.getDefault(), System.out);
3835
assertEquals(3, sample.getPuzzle().getPuzzle().getFacts().size());
3936
}
4037

0 commit comments

Comments
 (0)