432 lines
17 KiB
Java
432 lines
17 KiB
Java
package org.rosettacode.zebra;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
|
|
public class Zebra {
|
|
|
|
private static final int[] orders = {1, 2, 3, 4, 5};
|
|
private static final String[] nations = {"English", "Danish", "German", "Swedish", "Norwegian"};
|
|
private static final String[] animals = {"Zebra", "Horse", "Birds", "Dog", "Cats"};
|
|
private static final String[] drinks = {"Coffee", "Tea", "Beer", "Water", "Milk"};
|
|
private static final String[] cigarettes = {"Pall Mall", "Blend", "Blue Master", "Prince", "Dunhill"};
|
|
private static final String[] colors = {"Red", "Green", "White", "Blue", "Yellow"};
|
|
|
|
static class Solver {
|
|
private final PossibleLines puzzleTable = new PossibleLines();
|
|
|
|
void solve() {
|
|
PossibleLines constraints = new PossibleLines();
|
|
constraints.add(new PossibleLine(null, "English", "Red", null, null, null));
|
|
constraints.add(new PossibleLine(null, "Swedish", null, "Dog", null, null));
|
|
constraints.add(new PossibleLine(null, "Danish", null, null, "Tea", null));
|
|
constraints.add(new PossibleLine(null, null, "Green", null, "Coffee", null));
|
|
constraints.add(new PossibleLine(null, null, null, "Birds", null, "Pall Mall"));
|
|
constraints.add(new PossibleLine(null, null, "Yellow", null, null, "Dunhill"));
|
|
constraints.add(new PossibleLine(3, null, null, null, "Milk", null));
|
|
constraints.add(new PossibleLine(1, "Norwegian", null, null, null, null));
|
|
constraints.add(new PossibleLine(null, null, null, null, "Beer", "Blue Master"));
|
|
constraints.add(new PossibleLine(null, "German", null, null, null, "Prince"));
|
|
constraints.add(new PossibleLine(2, null, "Blue", null, null, null));
|
|
|
|
//Creating all possible combination of a puzzle line.
|
|
//The maximum number of lines is 5^^6 (15625).
|
|
//Each combination line is checked against a set of knowing facts, thus
|
|
//only a small number of line result at the end.
|
|
for (Integer orderId : Zebra.orders) {
|
|
for (String nation : Zebra.nations) {
|
|
for (String color : Zebra.colors) {
|
|
for (String animal : Zebra.animals) {
|
|
for (String drink : Zebra.drinks) {
|
|
for (String cigarette : Zebra.cigarettes) {
|
|
addPossibleNeighbors(constraints, orderId, nation, color, animal, drink, cigarette);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
System.out.println("After general rule set validation, remains " +
|
|
puzzleTable.size() + " lines.");
|
|
|
|
for (Iterator<PossibleLine> it = puzzleTable.iterator(); it.hasNext(); ) {
|
|
boolean validLine = true;
|
|
|
|
PossibleLine possibleLine = it.next();
|
|
|
|
if (possibleLine.leftNeighbor != null) {
|
|
PossibleLine neighbor = possibleLine.leftNeighbor;
|
|
if (neighbor.order < 1 || neighbor.order > 5) {
|
|
validLine = false;
|
|
it.remove();
|
|
}
|
|
}
|
|
if (validLine && possibleLine.rightNeighbor != null) {
|
|
PossibleLine neighbor = possibleLine.rightNeighbor;
|
|
if (neighbor.order < 1 || neighbor.order > 5) {
|
|
it.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
System.out.println("After removing out of bound neighbors, remains " +
|
|
puzzleTable.size() + " lines.");
|
|
|
|
//Setting left and right neighbors
|
|
for (PossibleLine puzzleLine : puzzleTable) {
|
|
for (PossibleLine leftNeighbor : puzzleLine.neighbors) {
|
|
PossibleLine rightNeighbor = leftNeighbor.copy();
|
|
|
|
//make it left neighbor
|
|
leftNeighbor.order = puzzleLine.order - 1;
|
|
if (puzzleTable.contains(leftNeighbor)) {
|
|
if (puzzleLine.leftNeighbor != null)
|
|
puzzleLine.leftNeighbor.merge(leftNeighbor);
|
|
else
|
|
puzzleLine.setLeftNeighbor(leftNeighbor);
|
|
}
|
|
rightNeighbor.order = puzzleLine.order + 1;
|
|
if (puzzleTable.contains(rightNeighbor)) {
|
|
if (puzzleLine.rightNeighbor != null)
|
|
puzzleLine.rightNeighbor.merge(rightNeighbor);
|
|
else
|
|
puzzleLine.setRightNeighbor(rightNeighbor);
|
|
}
|
|
}
|
|
}
|
|
|
|
int iteration = 1;
|
|
int lastSize = 0;
|
|
|
|
//Recursively validate against neighbor rules
|
|
while (puzzleTable.size() > 5 && lastSize != puzzleTable.size()) {
|
|
lastSize = puzzleTable.size();
|
|
puzzleTable.clearLineCountFlags();
|
|
|
|
recursiveSearch(null, puzzleTable, -1);
|
|
|
|
constraints.clear();
|
|
// Assuming we'll get at leas one valid line each iteration, we create
|
|
// a set of new rules with lines which have no more then one instance of same OrderId.
|
|
for (int i = 1; i < 6; i++) {
|
|
if (puzzleTable.getLineCountByOrderId(i) == 1)
|
|
constraints.addAll(puzzleTable.getSimilarLines(new PossibleLine(i, null, null, null, null,
|
|
null)));
|
|
}
|
|
|
|
puzzleTable.removeIf(puzzleLine -> !constraints.accepts(puzzleLine));
|
|
|
|
System.out.println("After " + iteration + " recursive iteration, remains "
|
|
+ puzzleTable.size() + " lines");
|
|
iteration++;
|
|
}
|
|
|
|
// Print the results
|
|
System.out.println("-------------------------------------------");
|
|
if (puzzleTable.size() == 5) {
|
|
for (PossibleLine puzzleLine : puzzleTable) {
|
|
System.out.println(puzzleLine.getWholeLine());
|
|
}
|
|
} else
|
|
System.out.println("Sorry, solution not found!");
|
|
}
|
|
|
|
private void addPossibleNeighbors(
|
|
PossibleLines constraints, Integer orderId, String nation,
|
|
String color, String animal, String drink, String cigarette) {
|
|
boolean validLine = true;
|
|
PossibleLine pzlLine = new PossibleLine(orderId,
|
|
nation,
|
|
color,
|
|
animal,
|
|
drink,
|
|
cigarette);
|
|
// Checking against a set of knowing facts
|
|
if (constraints.accepts(pzlLine)) {
|
|
// Adding rules of neighbors
|
|
if (cigarette.equals("Blend")
|
|
&& (animal.equals("Cats") || drink.equals("Water")))
|
|
validLine = false;
|
|
|
|
if (cigarette.equals("Dunhill")
|
|
&& animal.equals("Horse"))
|
|
validLine = false;
|
|
|
|
if (validLine) {
|
|
puzzleTable.add(pzlLine);
|
|
|
|
//set neighbors constraints
|
|
if (color.equals("Green")) {
|
|
pzlLine.setRightNeighbor(
|
|
new PossibleLine(null, null, "White", null, null, null));
|
|
}
|
|
if (color.equals("White")) {
|
|
pzlLine.setLeftNeighbor(
|
|
new PossibleLine(null, null, "Green", null, null, null));
|
|
}
|
|
//
|
|
if (animal.equals("Cats") && !cigarette.equals("Blend")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, null, null,
|
|
"Blend"));
|
|
}
|
|
if (cigarette.equals("Blend") && !animal.equals("Cats")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, "Cats", null
|
|
, null));
|
|
}
|
|
//
|
|
if (drink.equals("Water")
|
|
&& !animal.equals("Cats")
|
|
&& !cigarette.equals("Blend")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, null, null,
|
|
"Blend"));
|
|
}
|
|
|
|
if (cigarette.equals("Blend") && !drink.equals("Water")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, null, "Water"
|
|
, null));
|
|
}
|
|
//
|
|
if (animal.equals("Horse") && !cigarette.equals("Dunhill")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, null, null,
|
|
"Dunhill"));
|
|
}
|
|
if (cigarette.equals("Dunhill") && !animal.equals("Horse")) {
|
|
pzlLine.neighbors.add(new PossibleLine(null, null, null, "Horse",
|
|
null, null));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recursively checks the input set to ensure each line has right neighbor.
|
|
// Neighbors can be of three type, left, right or undefined.
|
|
// Direction: -1 left, 0 undefined, 1 right
|
|
private boolean recursiveSearch(PossibleLine pzzlNodeLine,
|
|
PossibleLines possibleLines, int direction) {
|
|
boolean validLeaf = false;
|
|
boolean hasNeighbor;
|
|
PossibleLines puzzleSubSet;
|
|
|
|
for (Iterator<PossibleLine> it = possibleLines.iterator(); it.hasNext(); ) {
|
|
PossibleLine pzzlLeafLine = it.next();
|
|
validLeaf = false;
|
|
|
|
hasNeighbor = pzzlLeafLine.hasNeighbor(direction);
|
|
|
|
if (hasNeighbor) {
|
|
puzzleSubSet = puzzleTable.getSimilarLines(pzzlLeafLine.getNeighbor(direction));
|
|
if (puzzleSubSet != null) {
|
|
if (pzzlNodeLine != null)
|
|
validLeaf = puzzleSubSet.contains(pzzlNodeLine);
|
|
else
|
|
validLeaf = recursiveSearch(pzzlLeafLine, puzzleSubSet, -1 * direction);
|
|
}
|
|
}
|
|
|
|
if (!validLeaf && pzzlLeafLine.hasNeighbor(-1 * direction)) {
|
|
hasNeighbor = true;
|
|
puzzleSubSet = puzzleTable.getSimilarLines(pzzlLeafLine.getNeighbor(-1 * direction));
|
|
if (puzzleSubSet != null) {
|
|
if (pzzlNodeLine != null)
|
|
validLeaf = puzzleSubSet.contains(pzzlNodeLine);
|
|
else
|
|
validLeaf = recursiveSearch(pzzlLeafLine, puzzleSubSet, direction);
|
|
}
|
|
}
|
|
|
|
if (pzzlNodeLine != null && validLeaf)
|
|
return true;
|
|
|
|
if (pzzlNodeLine == null && hasNeighbor && !validLeaf) {
|
|
it.remove();
|
|
}
|
|
|
|
if (pzzlNodeLine == null) {
|
|
if (hasNeighbor && validLeaf) {
|
|
possibleLines.riseLineCountFlags(pzzlLeafLine.order);
|
|
}
|
|
if (!hasNeighbor) {
|
|
possibleLines.riseLineCountFlags(pzzlLeafLine.order);
|
|
}
|
|
}
|
|
}
|
|
return validLeaf;
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
|
|
Solver solver = new Solver();
|
|
solver.solve();
|
|
}
|
|
|
|
static class PossibleLines extends LinkedHashSet<PossibleLine> {
|
|
|
|
private final int[] count = new int[5];
|
|
|
|
public PossibleLine get(int index) {
|
|
return ((PossibleLine) toArray()[index]);
|
|
}
|
|
|
|
public PossibleLines getSimilarLines(PossibleLine searchLine) {
|
|
PossibleLines puzzleSubSet = new PossibleLines();
|
|
for (PossibleLine possibleLine : this) {
|
|
if (possibleLine.getCommonFactsCount(searchLine) == searchLine.getFactsCount())
|
|
puzzleSubSet.add(possibleLine);
|
|
}
|
|
if (puzzleSubSet.isEmpty())
|
|
return null;
|
|
|
|
return puzzleSubSet;
|
|
}
|
|
|
|
public boolean contains(PossibleLine searchLine) {
|
|
for (PossibleLine puzzleLine : this) {
|
|
if (puzzleLine.getCommonFactsCount(searchLine) == searchLine.getFactsCount())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean accepts(PossibleLine searchLine) {
|
|
int passed = 0;
|
|
int notpassed = 0;
|
|
|
|
for (PossibleLine puzzleSetLine : this) {
|
|
int lineFactsCnt = puzzleSetLine.getFactsCount();
|
|
int comnFactsCnt = puzzleSetLine.getCommonFactsCount(searchLine);
|
|
|
|
if (lineFactsCnt != comnFactsCnt && lineFactsCnt != 0 && comnFactsCnt != 0) {
|
|
notpassed++;
|
|
}
|
|
|
|
if (lineFactsCnt == comnFactsCnt)
|
|
passed++;
|
|
}
|
|
return passed >= 0 && notpassed == 0;
|
|
}
|
|
|
|
public void riseLineCountFlags(int lineOrderId) {
|
|
count[lineOrderId - 1]++;
|
|
}
|
|
|
|
public void clearLineCountFlags() {
|
|
Arrays.fill(count, 0);
|
|
}
|
|
|
|
public int getLineCountByOrderId(int lineOrderId) {
|
|
return count[lineOrderId - 1];
|
|
}
|
|
}
|
|
|
|
static class PossibleLine {
|
|
|
|
Integer order;
|
|
String nation;
|
|
String color;
|
|
String animal;
|
|
String drink;
|
|
String cigarette;
|
|
|
|
PossibleLine rightNeighbor;
|
|
PossibleLine leftNeighbor;
|
|
Set<PossibleLine> neighbors = new LinkedHashSet<>();
|
|
|
|
public PossibleLine(Integer order, String nation, String color,
|
|
String animal, String drink, String cigarette) {
|
|
this.animal = animal;
|
|
this.cigarette = cigarette;
|
|
this.color = color;
|
|
this.drink = drink;
|
|
this.nation = nation;
|
|
this.order = order;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
return obj instanceof PossibleLine
|
|
&& getWholeLine().equals(((PossibleLine) obj).getWholeLine());
|
|
}
|
|
|
|
public int getFactsCount() {
|
|
int facts = 0;
|
|
facts += order != null ? 1 : 0;
|
|
facts += nation != null ? 1 : 0;
|
|
facts += color != null ? 1 : 0;
|
|
facts += animal != null ? 1 : 0;
|
|
facts += cigarette != null ? 1 : 0;
|
|
facts += drink != null ? 1 : 0;
|
|
return facts;
|
|
}
|
|
|
|
private static int common(Object a, Object b) {
|
|
return a != null && Objects.equals(a, b) ? 1 : 0;
|
|
}
|
|
|
|
public int getCommonFactsCount(PossibleLine facts) {
|
|
return common(order, facts.order)
|
|
+ common(nation, facts.nation)
|
|
+ common(color, facts.color)
|
|
+ common(animal, facts.animal)
|
|
+ common(cigarette, facts.cigarette)
|
|
+ common(drink, facts.drink);
|
|
}
|
|
|
|
public void setLeftNeighbor(PossibleLine leftNeighbor) {
|
|
this.leftNeighbor = leftNeighbor;
|
|
this.leftNeighbor.order = order - 1;
|
|
}
|
|
|
|
public void setRightNeighbor(PossibleLine rightNeighbor) {
|
|
this.rightNeighbor = rightNeighbor;
|
|
this.rightNeighbor.order = order + 1;
|
|
}
|
|
|
|
public boolean hasNeighbor(int direction) {
|
|
return getNeighbor(direction) != null;
|
|
}
|
|
|
|
public PossibleLine getNeighbor(int direction) {
|
|
if (direction < 0)
|
|
return leftNeighbor;
|
|
else
|
|
return rightNeighbor;
|
|
}
|
|
|
|
public String getWholeLine() {
|
|
return order + " - " +
|
|
nation + " - " +
|
|
color + " - " +
|
|
animal + " - " +
|
|
drink + " - " +
|
|
cigarette;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(order, nation, color, animal, drink, cigarette);
|
|
}
|
|
|
|
public void merge(PossibleLine mergedLine) {
|
|
if (order == null) order = mergedLine.order;
|
|
if (nation == null) nation = mergedLine.nation;
|
|
if (color == null) color = mergedLine.color;
|
|
if (animal == null) animal = mergedLine.animal;
|
|
if (drink == null) drink = mergedLine.drink;
|
|
if (cigarette == null) cigarette = mergedLine.cigarette;
|
|
}
|
|
|
|
public PossibleLine copy() {
|
|
PossibleLine clone = new PossibleLine(order, nation, color, animal, drink, cigarette);
|
|
clone.leftNeighbor = leftNeighbor;
|
|
clone.rightNeighbor = rightNeighbor;
|
|
clone.neighbors = neighbors; // shallow copy
|
|
return clone;
|
|
}
|
|
}
|
|
}
|