欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Revisit again: 一道应聘智力题的编程求解, Einstein puzzle

程序员文章站 2022-03-17 11:13:14
...

Continue on: http://jellyfish.iteye.com/admin/blogs/610841, but this time we take a different approach, using ranged variables.

 

First, define all the attributes we need with a range. The reason we need this twist is that later on we can "rule out" (remove values which are not possible based on deduction).

 

import java.util.HashSet;

public class Nationality
{
    public static final Nationality BRITISH = new Nationality("British");
    public static final Nationality GERMAN = new Nationality("German");
    public static final Nationality DENMARK = new Nationality("Denmark");
    public static final Nationality SWEDISH = new Nationality("Swedish");
    public static final Nationality NORWEGIAN = new Nationality("Norwegian");

    public static final Nationality[] ALL = new Nationality[]
            { BRITISH, GERMAN, DENMARK, SWEDISH, NORWEGIAN };

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<Nationality> range = new HashSet<Nationality>();

    private Nationality(String name)
    {
        this.name = name;
    }

    public static Nationality unknown()
    {
        Nationality ret = new Nationality(UNKNOWN);
        ret.addRange(BRITISH);
        ret.addRange(GERMAN);
        ret.addRange(DENMARK);
        ret.addRange(SWEDISH);
        ret.addRange(NORWEGIAN);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(Nationality nationality)
    {
        this.range.add(nationality);
    }

    public void removeRange(HashSet<Nationality> range)
    {
        this.range.removeAll(range);
    }

    public void removeRange(Nationality nationality)
    {
        this.range.remove(nationality);
    }

    public boolean contains(Nationality nationality)
    {
        return range.contains(nationality);
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Nationality)) return false;

        Nationality n = (Nationality) obj;
        return this.name.equals(n.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (Nationality a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

 

and

 

import java.util.HashSet;

public class Pet
{
    public static final Pet CAT = new Pet("Cat");
    public static final Pet DOG = new Pet("Dog");
    public static final Pet FISH = new Pet("Fish");
    public static final Pet BIRD = new Pet("Bird");
    public static final Pet HORSE = new Pet("Horse");

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<Pet> range = new HashSet<Pet>();

    private Pet(String name)
    {
        this.name = name;
    }

    public static Pet unknown()
    {
        Pet ret = new Pet(UNKNOWN);
        ret.addRange(CAT);
        ret.addRange(DOG);
        ret.addRange(FISH);
        ret.addRange(BIRD);
        ret.addRange(HORSE);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(Pet pet)
    {
        this.range.add(pet);
    }

    public boolean removeRange(HashSet<Pet> range)
    {
        return this.range.removeAll(range);
    }

    public boolean removeRange(Pet pet)
    {
        return this.range.remove(pet);
    }

    public boolean contains(Pet pet)
    {
        return range.contains(pet);
    }

    public HashSet<Pet> getRange()
    {
        return range;
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Pet)) return false;

        Pet p = (Pet) obj;
        return this.name.equals(p.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (Pet a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

 

and

import java.util.HashSet;

public class HouseColor
{
    public static final HouseColor RED = new HouseColor("Red");
    public static final HouseColor GREEN = new HouseColor("Green");
    public static final HouseColor YELLOW = new HouseColor("Yellow");
    public static final HouseColor WHITE = new HouseColor("White");
    public static final HouseColor BLUE = new HouseColor("Blue");

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<HouseColor> range = new HashSet<HouseColor>();

    private HouseColor(String name)
    {
        this.name = name;
    }

    public static HouseColor unknown()
    {
        HouseColor ret = new HouseColor(UNKNOWN);
        ret.addRange(RED);
        ret.addRange(GREEN);
        ret.addRange(YELLOW);
        ret.addRange(WHITE);
        ret.addRange(BLUE);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(HouseColor houseColor)
    {
        this.range.add(houseColor);
    }

    public void removeRange(HashSet<HouseColor> range)
    {
        this.range.removeAll(range);
    }

    public boolean removeRange(HouseColor houseColor)
    {
        return this.range.remove(houseColor);
    }

    public boolean contains(HouseColor houseColor)
    {
        return range.contains(houseColor);
    }

    public HashSet<HouseColor> getRange()
    {
        return range;
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof HouseColor)) return false;

        HouseColor c = (HouseColor) obj;
        return this.name.equals(c.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (HouseColor a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

 

and

import java.util.HashSet;

public class Cigarette
{
    public static final Cigarette PALL_MALL = new Cigarette("Pall Mall");
    public static final Cigarette DUNHILL = new Cigarette("Dunhill");
    public static final Cigarette BLUE_MASTER = new Cigarette("Blue Master");
    public static final Cigarette PRINCE = new Cigarette("Prince");
    public static final Cigarette BLEND = new Cigarette("Blend");

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<Cigarette> range = new HashSet<Cigarette>();

    private Cigarette(String name)
    {
        this.name = name;
    }

    public static Cigarette unknown()
    {
        Cigarette ret = new Cigarette(UNKNOWN);
        ret.addRange(PALL_MALL);
        ret.addRange(DUNHILL);
        ret.addRange(BLUE_MASTER);
        ret.addRange(PRINCE);
        ret.addRange(BLEND);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(Cigarette cigarette)
    {
        this.range.add(cigarette);
    }

    public void removeRange(HashSet<Cigarette> range)
    {
        this.range.removeAll(range);
    }

    public boolean removeRange(Cigarette cigarette)
    {
        return range.remove(cigarette);
    }

    public boolean contains(Cigarette cigarette)
    {
        return range.contains(cigarette);
    }

    public HashSet<Cigarette> getRange()
    {
        return range;
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Cigarette)) return false;

        Cigarette c = (Cigarette) obj;
        return this.name.equals(c.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (Cigarette a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

 

and

import java.util.HashSet;

public class Drink
{
    public static final Drink BEER = new Drink("Beer");
    public static final Drink TEA = new Drink("Tea");
    public static final Drink MILK = new Drink("Milk");
    public static final Drink WATER = new Drink("Water");
    public static final Drink COFFEE = new Drink("Coffee");

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<Drink> range = new HashSet<Drink>();

    private Drink(String name)
    {
        this.name = name;
    }

    public static Drink unknown()
    {
        Drink ret = new Drink(UNKNOWN);
        ret.addRange(BEER);
        ret.addRange(TEA);
        ret.addRange(MILK);
        ret.addRange(WATER);
        ret.addRange(COFFEE);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(Drink drink)
    {
        this.range.add(drink);
    }

    public void removeRange(HashSet<Drink> range)
    {
        this.range.removeAll(range);
    }

    public boolean removeRange(Drink drink)
    {
        return this.range.remove(drink);
    }

    public boolean contains(Drink drink)
    {
        return range.contains(drink);
    }

    public HashSet<Drink> getRange()
    {
        return range;
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof Drink)) return false;

        Drink d = (Drink) obj;
        return this.name.equals(d.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (Drink a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

 

and

import java.util.HashSet;

public class HouseLocation
{
    public static final HouseLocation ONE = new HouseLocation("1");
    public static final HouseLocation TWO = new HouseLocation("2");
    public static final HouseLocation THREE = new HouseLocation("3");
    public static final HouseLocation FOUR = new HouseLocation("4");
    public static final HouseLocation FIVE = new HouseLocation("5");

    private static final String UNKNOWN = "Unknown";

    private String name;
    private HashSet<HouseLocation> range = new HashSet<HouseLocation>();

    private HouseLocation(String name)
    {
        this.name = name;
    }

    public static HouseLocation unknown()
    {
        HouseLocation ret = new HouseLocation(UNKNOWN);
        ret.addRange(ONE);
        ret.addRange(TWO);
        ret.addRange(THREE);
        ret.addRange(FOUR);
        ret.addRange(FIVE);

        return ret;
    }

    public boolean isKnown()
    {
        return !this.name.equals(UNKNOWN);
    }

    public void addRange(HouseLocation houseLocation)
    {
        this.range.add(houseLocation);
    }

    public boolean removeRange(HashSet<HouseLocation> range)
    {
        return this.range.removeAll(range);
    }

    public boolean removeRange(HouseLocation houseLocation)
    {
        return this.range.remove(houseLocation);
    }

    public boolean contains(HouseLocation houseLocation)
    {
        return range.contains(houseLocation);
    }

    public HashSet<HouseLocation> getRange()
    {
        return range;
    }

    public HouseLocation next()
    {
        if (this == ONE) return TWO;
        else if (this == TWO) return THREE;
        else if (this == THREE) return FOUR;
        else if (this == FOUR) return FIVE;
        else return unknown();
    }
    public HouseLocation previous()
    {
        if (this == TWO) return ONE;
        else if (this == THREE) return TWO;
        else if (this == FOUR) return THREE;
        else if (this == FIVE) return FOUR;
        else return unknown();
    }

    public boolean isLarger(HouseLocation loc)
    {
        if (!loc.isKnown()) throw new RuntimeException("try to compare to unknown!");

        int a = Integer.parseInt(name);
        int b = Integer.parseInt(loc.name);
        return a > b;
    }

    public int hashCode() { return name.hashCode(); }

    public boolean equals(Object obj)
    {
        if (!(obj instanceof HouseLocation)) return false;

        HouseLocation c = (HouseLocation) obj;
        return this.name.equals(c.name);
    }

    public String toString()
    {
        if (range.size() == 0) return name;

        String ret = "";
        for (HouseLocation a : range) ret += a + ",";
        return name + "{" + ret.substring(0, ret.length()-1) + "}";
    }
}

 

These classes more or less have the same structure. Now we use these classes to define the Person class:

 

public class Person
{
    public Nationality nationality;
    public HouseColor houseColor;
    public Pet pet;
    public Drink drink;
    public Cigarette cigarette;
    public HouseLocation houseLocation;
    public Person leftNeighbour;
    public Person rightNeighbour;

    public Person(boolean hasNeighbour)
    {
        nationality = Nationality.unknown();
        houseColor = HouseColor.unknown();
        pet = Pet.unknown();
        drink = Drink.unknown();
        cigarette = Cigarette.unknown();
        houseLocation = HouseLocation.unknown();
        if (hasNeighbour)
        {
            leftNeighbour = new Person(false); // null means no neighbour on this side
            rightNeighbour = new Person(false); // null means no neighbour on this side
        }
    }

    public String toString()
    {
        return "{" + nationality + ":" + houseColor + ":" + pet + ":" + drink + ":"
                + cigarette + ":" + houseLocation + "}" ;
    }
}

 Here the only thing unusual is the if block in the constructor because we don't want infinite recursion.

 

Now we define the clue interface:

import java.util.HashSet;

public interface Clue
{
    void apply(HashSet<Person> state);
}

 

The clue will apply to a Person set (we could use an ArrayList to avoid the hashing implicitly called by the HashSet).

 

Now the fun part is how to interpret the clues in code. As we know, some of the clues are simple and straight forward, but there are a few complex ones. Given the context of the previous post, I won't explain them

 

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseColor;

import java.util.HashSet;

/**
 * Set to british the color to red to remove red from all other possibilities.
 * This rule should apply only once for performance reason since once is
 * enough.
 */
public class Clue01BritishIsRed implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.nationality == Nationality.BRITISH)
            {
                p.houseColor = HouseColor.RED;
            }
            else
            {
                if (p.houseColor.removeRange(HouseColor.RED))
                    applied = true;
            }
        }

        if (applied)
            System.out.println("rule 01 applied: British hourse color is red.");
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;
import my.test.brainteaser.einsteinpuzzle.rulebased.Pet;

import java.util.HashSet;

/**
 * set to the swedish the dog and remove dog from all other probabilities
 */
public class Clue02SwedishHasDog implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.nationality == Nationality.SWEDISH)
            {
                p.pet = Pet.DOG;
            }
            else
            {
                if (p.pet.removeRange(Pet.DOG))
                    applied = true;
            }
        }

        if (applied)
            System.out.println("rule 02 applied: Swedish has dog.");
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;
import my.test.brainteaser.einsteinpuzzle.rulebased.Drink;

import java.util.HashSet;

public class Clue03DenmarkDrinkTea implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.nationality == Nationality.DENMARK)
            {
                p.drink = Drink.TEA;
            }
            else
            {
                if (p.drink.removeRange(Drink.TEA))
                    applied = true;

            }
        }

        if (applied)
            System.out.println("rule 03 applied: Denmark drinks tea.");
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseColor;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseLocation;

import java.util.HashSet;
import java.util.Iterator;

public class Clue04GreenHouseIsLeftOfWhiteHouse implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        Person greenHouse = null, whiteHouse = null;
        for (Person p : state)
        {
            if (p.houseColor == HouseColor.GREEN)
            {
                greenHouse = p;
                p.rightNeighbour.houseColor = HouseColor.WHITE;
            }
            else if (p.rightNeighbour == null)
            {
                if (p.houseColor.removeRange(HouseColor.GREEN))
                    applied = true;
            }
            else if (!p.rightNeighbour.houseColor.contains(HouseColor.WHITE))
            {
                if (p.houseColor.removeRange(HouseColor.GREEN))
                    applied = true;
            }
            
            if (p.houseColor == HouseColor.WHITE)
            {
                whiteHouse = p;
                p.leftNeighbour.houseColor = HouseColor.GREEN;
            }
            else if (p.leftNeighbour == null)
            {
                if (p.houseColor.removeRange(HouseColor.WHITE))
                    applied = true;
            }
            else if (!p.leftNeighbour.houseColor.contains(HouseColor.GREEN))
            {
                if (p.houseColor.removeRange(HouseColor.WHITE))
                    applied = true;
            }

            if (greenHouse != null && whiteHouse != null)
            {
                HashSet<HouseLocation> locations = greenHouse.houseLocation.getRange();
                if (locations.size() == 2 &&
                    locations.containsAll(whiteHouse.houseLocation.getRange())) // they have same range.
                {
                    Iterator<HouseLocation> it = greenHouse.houseLocation.getRange().iterator();
                    HouseLocation ha = it.next();
                    HouseLocation hb = it.next();
                    if (ha.isLarger(hb))
                    {
                        greenHouse.houseLocation = hb;
                        whiteHouse.houseLocation = ha;
                    }
                    else
                    {
                        greenHouse.houseLocation = ha;
                        whiteHouse.houseLocation = hb;
                    }
                }
            }
        }

        if (applied)
                System.out.println("rule 04 applied: Green house is left neighbour of white house.");
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseColor;
import my.test.brainteaser.einsteinpuzzle.rulebased.Drink;

import java.util.HashSet;

public class Clue05GreenHouseHostDrinkCoffee implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.houseColor == HouseColor.GREEN)
            {
                p.drink = Drink.COFFEE;
                for (Person pp : state)
                    if (pp.drink.removeRange(Drink.COFFEE))
                        applied = true;
            }
            else if (!p.houseColor.contains(HouseColor.GREEN))
            {
                if (p.drink.removeRange(Drink.COFFEE))
                    applied = true;
            }

            if (p.drink == Drink.COFFEE)
            {
                p.houseColor = HouseColor.GREEN;
                for (Person pp : state)
                    if (pp.houseColor.removeRange(HouseColor.GREEN))
                        applied = true;
            }
            else if (!p.drink.contains(Drink.COFFEE))
            {
                if (p.houseColor.removeRange(HouseColor.GREEN))
                    applied = true;
            }
        }

        if (applied)
            System.out.println("rule 05 applied: Green house host drinks coffee");
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Pet;
import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;

import java.util.HashSet;

public class Clue06PallMallSmokerHasBird implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.cigarette == Cigarette.PALL_MALL)
            {
                p.pet = Pet.BIRD;
                for (Person pp : state)
                    pp.pet.removeRange(Pet.BIRD);
            }
            else if (!p.cigarette.contains(Cigarette.PALL_MALL))
                p.pet.removeRange(Pet.BIRD);

            if (p.pet == Pet.BIRD)
            {
                p.cigarette = Cigarette.PALL_MALL;
                for (Person pp : state)
                    pp.cigarette.removeRange(Cigarette.PALL_MALL);
            }
            else if (!p.pet.contains(Pet.BIRD))
                p.cigarette.removeRange(Cigarette.PALL_MALL);
        }
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseColor;

import java.util.HashSet;

public class CLue07YellowHouseHostSmokeDunhill implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.houseColor == HouseColor.YELLOW)
            {
                p.cigarette = Cigarette.DUNHILL;
                for (Person pp : state)
                    pp.cigarette.removeRange(Cigarette.DUNHILL);
            }
            else if (!p.houseColor.contains(HouseColor.YELLOW))
                p.cigarette.removeRange(Cigarette.DUNHILL);

            if (p.cigarette == Cigarette.DUNHILL)
            {
                p.houseColor = HouseColor.YELLOW;
                for (Person pp : state)
                    pp.houseColor.removeRange(HouseColor.YELLOW);
            }
            else if (!p.cigarette.contains(Cigarette.DUNHILL))
                p.houseColor.removeRange(HouseColor.YELLOW);
        }
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Drink;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseLocation;

import java.util.HashSet;

public class Clue08MiddleHouseHostDrinkMilk implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.houseLocation == HouseLocation.THREE)
            {
                p.drink = Drink.MILK;
                for (Person pp : state)
                    pp.drink.removeRange(Drink.MILK);
            }
            else if (!p.houseLocation.contains(HouseLocation.THREE))
                p.drink.removeRange(Drink.MILK);

            if (p.drink == Drink.MILK)
            {
                p.houseLocation = HouseLocation.THREE;
                for (Person pp : state)
                    pp.houseLocation.removeRange(HouseLocation.THREE);
            }
            else if (!p.drink.contains(Drink.MILK))
                p.houseLocation.removeRange(HouseLocation.THREE);
        }
    }
}

 and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseLocation;

import java.util.HashSet;

public class Clue09NorwegianLiveIn1stHouse implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.nationality == Nationality.NORWEGIAN)
            {
                p.houseLocation = HouseLocation.ONE;
                p.leftNeighbour = null;
            }
            else
            {
                if (p.houseLocation.removeRange(HouseLocation.ONE))
                    applied = true;
            }
        }

        if (applied)
            System.out.println("rule 09 applied: Norwegian lives in the 1st housr.");
    }
}

 

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Pet;

import java.util.HashSet;

public class Clue10BlendSmokerIsNeighbourOfCatOwner implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.cigarette == Cigarette.BLEND)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.pet = Pet.CAT;
                else if (p.rightNeighbour == null) p.leftNeighbour.pet = Pet.CAT;
                else if (p.leftNeighbour.pet.isKnown() && p.leftNeighbour.pet != Pet.CAT)
                    p.rightNeighbour.pet = Pet.CAT;
                else if (p.rightNeighbour.pet.isKnown() && p.rightNeighbour.pet != Pet.CAT)
                    p.leftNeighbour.pet = Pet.CAT;
            }
            else if (p.cigarette.contains(Cigarette.BLEND))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.pet.contains(Pet.CAT)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.pet.contains(Pet.CAT)))
                    p.cigarette.removeRange(Cigarette.BLEND);
            }
            else if (p.pet == Pet.CAT)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.cigarette = Cigarette.BLEND;
                else if (p.rightNeighbour == null) p.leftNeighbour.cigarette = Cigarette.BLEND;
                else if (p.leftNeighbour.cigarette.isKnown() && p.leftNeighbour.cigarette != Cigarette.BLEND)
                    p.rightNeighbour.cigarette = Cigarette.BLEND;
                else if (p.rightNeighbour.cigarette.isKnown() && p.rightNeighbour.cigarette != Cigarette.BLEND)
                    p.leftNeighbour.cigarette = Cigarette.BLEND;
            }
            else if (p.pet.contains(Pet.CAT))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.cigarette.contains(Cigarette.BLEND)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.cigarette.contains(Cigarette.BLEND)))
                    p.pet.removeRange(Pet.CAT);
            }
        }
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Pet;

import java.util.HashSet;

public class Clue11HorseOwnerIsNeighbourOfDunhillSmoker implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.cigarette == Cigarette.DUNHILL)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.pet = Pet.HORSE;
                else if (p.rightNeighbour == null) p.leftNeighbour.pet = Pet.HORSE;
                else if (p.leftNeighbour.pet.isKnown() && p.leftNeighbour.pet != Pet.HORSE)
                    p.rightNeighbour.pet = Pet.HORSE;
                else if (p.rightNeighbour.pet.isKnown() && p.rightNeighbour.pet != Pet.HORSE)
                    p.leftNeighbour.pet = Pet.HORSE;
            }
            else if (p.cigarette.contains(Cigarette.DUNHILL))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.pet.contains(Pet.HORSE)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.pet.contains(Pet.HORSE)))
                    p.cigarette.removeRange(Cigarette.DUNHILL);
            }

            if (p.pet == Pet.HORSE)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.cigarette = Cigarette.DUNHILL;
                else if (p.rightNeighbour == null) p.leftNeighbour.cigarette = Cigarette.DUNHILL;
                else if (p.leftNeighbour.cigarette.isKnown() && p.leftNeighbour.cigarette != Cigarette.DUNHILL)
                    p.rightNeighbour.cigarette = Cigarette.DUNHILL;
                else if (p.rightNeighbour.cigarette.isKnown() && p.rightNeighbour.cigarette != Cigarette.DUNHILL)
                    p.leftNeighbour.cigarette = Cigarette.DUNHILL;
            }
            else if (p.pet.contains(Pet.HORSE))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.cigarette.contains(Cigarette.DUNHILL)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.cigarette.contains(Cigarette.DUNHILL)))
                    p.pet.removeRange(Pet.HORSE);
            }
        }
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Drink;

import java.util.HashSet;

public class Clue12BlueMasterSmokerDrinkBeer implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.cigarette == Cigarette.BLUE_MASTER)
            {
                p.drink = Drink.BEER;
                for (Person pp : state)
                    pp.drink.removeRange(Drink.BEER);
            }
            else if (!p.cigarette.contains(Cigarette.BLUE_MASTER))
                p.drink.removeRange(Drink.BEER);

            if (p.drink == Drink.BEER)
            {
                p.cigarette = Cigarette.BLUE_MASTER;
                for (Person pp : state)
                    pp.cigarette.removeRange(Cigarette.BLUE_MASTER);
            }
            else if (!p.drink.contains(Drink.BEER))
                p.cigarette.removeRange(Cigarette.BLUE_MASTER);
        }
    }
}

 

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;

import java.util.HashSet;

public class Clue13GermanSmokePrince implements Clue
{
    public void apply(HashSet<Person> state)
    {
        boolean applied = false;

        for (Person p : state)
        {
            if (p.nationality == Nationality.GERMAN)
            {
                p.cigarette = Cigarette.PRINCE;
            }
            else
            {
                if (p.cigarette.removeRange(Cigarette.PRINCE))
                    applied = true;
            }
        }

        if (applied)
            System.out.println("rule 13 applied: German smokes prince.");
    }
}

 

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Nationality;
import my.test.brainteaser.einsteinpuzzle.rulebased.HouseColor;

import java.util.HashSet;

public class Clue14NorwegianIsNeighbourOfBlueHouse implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.nationality.equals(Nationality.NORWEGIAN))
            {
                p.houseColor.removeRange(HouseColor.BLUE);

                if (p.leftNeighbour == null)
                    p.rightNeighbour.houseColor = HouseColor.BLUE;
                else if (p.leftNeighbour.houseColor.isKnown() && p.leftNeighbour.houseColor != HouseColor.BLUE)
                    p.rightNeighbour.houseColor = HouseColor.BLUE;
                else if (p.rightNeighbour == null)
                    p.leftNeighbour.houseColor = HouseColor.BLUE;
                else if (p.rightNeighbour.houseColor.isKnown() && p.rightNeighbour.houseColor != HouseColor.BLUE)
                    p.leftNeighbour.houseColor = HouseColor.BLUE;
            }
        }
    }
}

 

and

import my.test.brainteaser.einsteinpuzzle.rulebased.Cigarette;
import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;
import my.test.brainteaser.einsteinpuzzle.rulebased.Person;
import my.test.brainteaser.einsteinpuzzle.rulebased.Drink;

import java.util.HashSet;

public class Clue15BlendSmokerIsNeighourOfWaterDrinker implements Clue
{
    public void apply(HashSet<Person> state)
    {
        for (Person p : state)
        {
            if (p.cigarette == Cigarette.BLEND)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.drink = Drink.WATER;
                else if (p.rightNeighbour == null) p.leftNeighbour.drink = Drink.WATER;
                else if (p.leftNeighbour.drink.isKnown() && p.leftNeighbour.drink != Drink.WATER)
                    p.rightNeighbour.drink = Drink.WATER;
                else if (p.rightNeighbour.drink.isKnown() && p.rightNeighbour.drink != Drink.WATER)
                    p.leftNeighbour.drink = Drink.WATER;                
            }
            else if (p.cigarette.contains(Cigarette.BLEND))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.drink.contains(Drink.WATER)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.drink.contains(Drink.WATER)))
                    p.cigarette.removeRange(Cigarette.BLEND);
            }

            if (p.drink == Drink.WATER)
            {
                if (p.leftNeighbour == null) p.rightNeighbour.cigarette = Cigarette.BLEND;
                else if (p.rightNeighbour == null) p.leftNeighbour.cigarette = Cigarette.BLEND;
                else if (p.leftNeighbour.cigarette.isKnown() && p.leftNeighbour.cigarette != Cigarette.BLEND)
                    p.rightNeighbour.cigarette = Cigarette.BLEND;
                else if (p.rightNeighbour.cigarette.isKnown() && p.rightNeighbour.cigarette != Cigarette.BLEND)
                    p.leftNeighbour.cigarette = Cigarette.BLEND;
            }
            else if (p.drink.contains(Drink.WATER))
            {
                if ((p.leftNeighbour == null || !p.leftNeighbour.cigarette.contains(Cigarette.BLEND)) &&
                    (p.rightNeighbour == null || !p.rightNeighbour.cigarette.contains(Cigarette.BLEND)))
                    p.drink.removeRange(Drink.WATER);
            }
        }        
    }
}

 

A factory class to initiate all of them:

 

import my.test.brainteaser.einsteinpuzzle.rulebased.Clue;

public class ClueFactory
{
    public static Clue[] clues = new Clue[] {
            new Clue01BritishIsRed(),
            new Clue02SwedishHasDog(),
            new Clue03DenmarkDrinkTea(),
            new Clue04GreenHouseIsLeftOfWhiteHouse(),
            new Clue05GreenHouseHostDrinkCoffee(),
            new Clue06PallMallSmokerHasBird(),
            new CLue07YellowHouseHostSmokeDunhill(),
            new Clue08MiddleHouseHostDrinkMilk(),
            new Clue09NorwegianLiveIn1stHouse(),
            new Clue10BlendSmokerIsNeighbourOfCatOwner(),
            new Clue11HorseOwnerIsNeighbourOfDunhillSmoker(),
            new Clue12BlueMasterSmokerDrinkBeer(),
            new Clue13GermanSmokePrince(),
            new Clue14NorwegianIsNeighbourOfBlueHouse(),
            new Clue15BlendSmokerIsNeighourOfWaterDrinker()
    };
    
    public static Clue[] create()
    {
        return clues;
    }
}

 

With all these, we are ready for the puzzle class:

 

import my.test.brainteaser.einsteinpuzzle.rulebased.clues.ClueFactory;

import java.util.HashSet;

/**
 * Einstein Brain Teaser
 *
 * Rules:
 * 1. There are 5 houses in 5 different colours.
 * 2. In each house lives a person with a different nationality.
 * 3. These 5 owners drink a certain beverage, smoke a certain brand of cigarette and keep a certain pet.
 * 4. No owners have the same pet, brand of cigarette, or drink.
 *
 * Clues:
 * 1. The Brit lives in a red house
 * 2. The Swede keeps a dog
 * 3. The Dane drinks tea
 * 4. The green house is on the left of the white house.
 * 5. The green house owner drinks coffee.
 * 6. The person who smokes Pall Mall keeps birds.
 * 7. The owner of the yellow house smokes Dunhill.
 * 8. The man living in the house right in the center drinks milk
 * 9. The Norwegian lives in the first house.
 * 10. The man who smokes Blend lives next to the one who keeps cats
 * 11. The man who keeps horses lives next to the man who smokes Dunhill
 * 12. The owner who smokes Camel drinks beer
 * 13. The German smokes Marlborough.
 * 14. The Norwegian lives next to the blue house
 * 15. The man who smokes Blend has a neighbour who drinks water.
 */
public class EinsteinPuzzle
{
    public static void main(String[] args)
    {
        long begin = System.currentTimeMillis();

        EinsteinPuzzle puzzle = new EinsteinPuzzle();
        puzzle.solve();

        System.out.println("time taken=" + (System.currentTimeMillis() - begin));
    }

    public void solve()
    {
        HashSet<Person> states = init();
        Clue[] clues = ClueFactory.clues;
        int limit = 1;

        do
        {
            for (Clue c : clues)
            {
                c.apply(states);
            }

            reconAttributes(states);

            reconPeople(states);

            System.out.println("step=" + limit);
            print(states);

            limit++;
        } while (checkAttributes(states));
    }

    private void reconAttributes(HashSet<Person> states)
    {
        for (Person p : states)
        {
            if (p.houseColor.getRange().size() == 1)
            {
                p.houseColor = p.houseColor.getRange().iterator().next();
                for (Person pp : states)
                    pp.houseColor.removeRange(p.houseColor);
            }
            if (p.houseLocation.getRange().size() == 1)
            {
                p.houseLocation = p.houseLocation.getRange().iterator().next();
                for (Person pp : states)
                    pp.houseLocation.removeRange(p.houseLocation);
            }
            if (p.pet.getRange().size() == 1)
            {
                p.pet = p.pet.getRange().iterator().next();
                for (Person pp : states)
                    pp.pet.removeRange(p.pet);
            }
            if (p.drink.getRange().size() == 1)
            {
                p.drink = p.drink.getRange().iterator().next();
                for (Person pp : states)
                    pp.drink.removeRange(p.drink);
            }
            if (p.cigarette.getRange().size() == 1)
            {
                p.cigarette = p.cigarette.getRange().iterator().next();
                for (Person pp : states)
                    pp.cigarette.removeRange(p.cigarette);
            }
        }
    }

    private void reconPeople(HashSet<Person> states)
    {
        for (Person p : states)
        {
            HashSet<Person> people = new HashSet<Person>(states);
            if (p.leftNeighbour != null)
            {
                removeUnmatched(states, people, p.leftNeighbour);
            }

            if (people.size() == 1)
            {
                Person n = people.iterator().next();
                merge(n, p.leftNeighbour, states);
                p.leftNeighbour = n;
                if (p.houseLocation.isKnown())
                    n.houseLocation = p.houseLocation.previous();
            }
        }
                          
        for (Person p : states)
        {
            HashSet<Person> people = new HashSet<Person>(states);
            if (p.rightNeighbour != null)
            {
                removeUnmatched(states, people, p.rightNeighbour);
            }
            if (people.size() == 1)
            {
                Person n = people.iterator().next();
                merge(n, p.rightNeighbour, states);
                p.rightNeighbour = n;
                if (p.houseLocation.isKnown())
                    n.houseLocation = p.houseLocation.next();
            }
        }
    }

    private void merge(Person mergeTo, Person toBeMerged, HashSet<Person> people)
    {
        if (toBeMerged.nationality.isKnown())
        {
            mergeTo.nationality = toBeMerged.nationality;
            for (Person p : people)
            {
                p.nationality.removeRange(toBeMerged.nationality);
            }
        }
        if (toBeMerged.houseColor.isKnown())
        {
            mergeTo.houseColor = toBeMerged.houseColor;
            for (Person p : people)
            {
                p.houseColor.removeRange(toBeMerged.houseColor);
            }
        }
        if (toBeMerged.houseLocation.isKnown())
        {
            mergeTo.houseLocation = toBeMerged.houseLocation;
            for (Person p : people)
            {
                p.houseLocation.removeRange(toBeMerged.houseLocation);
            }
        }
        if (toBeMerged.pet.isKnown())
        {
            mergeTo.pet = toBeMerged.pet;
            for (Person p : people)
            {
                p.pet.removeRange(toBeMerged.pet);
            }
        }
        if (toBeMerged.drink.isKnown())
        {
            mergeTo.drink = toBeMerged.drink;
            for (Person p : people)
            {
                p.drink.removeRange(toBeMerged.drink);
            }
        }
        if (toBeMerged.cigarette.isKnown())
        {
            mergeTo.cigarette = toBeMerged.cigarette;
            for (Person p : people)
            {
                p.cigarette.removeRange(toBeMerged.cigarette);
            }
        }
    }
    private void removeUnmatched(HashSet<Person> states, HashSet<Person> people, Person neighbour)
    {
        if (neighbour.houseColor.isKnown())
        {
            for (Person pp : states)
            {
                if (pp.houseColor != neighbour.houseColor &&
                    !pp.houseColor.contains(neighbour.houseColor))
                    people.remove(pp);
            }
        }

        if (neighbour.houseLocation.isKnown())
        {
            for (Person pp : states)
            {
                if (pp.houseLocation != neighbour.houseLocation &&
                    !pp.houseLocation.contains(neighbour.houseLocation))
                    people.remove(pp);
            }
        }

        if (neighbour.pet.isKnown())
        {
            for (Person pp : states)
            {
                if (pp.pet != neighbour.pet &&
                    !pp.pet.contains(neighbour.pet))
                    people.remove(pp);
            }
        }

        if (neighbour.drink.isKnown())
        {
            for (Person pp : states)
            {
                if (pp.drink != neighbour.drink &&
                    !pp.drink.contains(neighbour.drink))
                    people.remove(pp);
            }
        }

        if (neighbour.cigarette.isKnown())
        {
            for (Person pp : states)
            {
                if (pp.cigarette != neighbour.cigarette &&
                    !pp.cigarette.contains(neighbour.cigarette))
                    people.remove(pp);
            }
        }
    }

    private void print(HashSet<Person> states)
    {
        int i=0;
        for (Person p : states)
        {
            System.out.println("state(" + i + ")=" + p);
            i++;
        }
        System.out.println("========================================================");
    }

    private boolean checkAttributes(HashSet<Person> states)
    {
        for (Person p : states)
        {
            if (!p.houseColor.isKnown()) return true;
            if (!p.houseLocation.isKnown()) return true;
            if (!p.pet.isKnown()) return true;
            if (!p.drink.isKnown()) return true;
            if (!p.cigarette.isKnown()) return true;
        }

        return false;
    }

    public HashSet<Person> init()
    {
        HashSet<Person> initialState = new HashSet<Person>();

        Person p;
        for (Nationality n : Nationality.ALL)
        {
            p = new Person(true);
            p.nationality = n;
            initialState.add(p);
        }

        return initialState;
    }
}

 

I believe I forget the package names, but you can easily fix that.

 

The result is like this:

 

rule 01 applied: British hourse color is red.
rule 02 applied: Swedish has dog.
rule 03 applied: Denmark drinks tea.
rule 05 applied: Green house host drinks coffee
rule 09 applied: Norwegian lives in the 1st housr.
rule 13 applied: German smokes prince.
step=1
state(0)={German:Unknown{Blue,White,Green,Yellow}:Unknown{Bird,Cat,Horse,Fish}:Unknown{Coffee,Beer,Milk,Water}:Prince:Unknown{3,2,5,4}}
state(1)={Denmark:Unknown{Blue,White,Yellow}:Unknown{Bird,Cat,Horse,Fish}:Tea:Unknown{Pall Mall,Blend,Dunhill}:Unknown{2,5,4}}
state(2)={Norwegian:Unknown{White,Green,Yellow}:Unknown{Bird,Cat,Horse,Fish}:Unknown{Coffee,Beer,Milk,Water}:Unknown{Blue Master,Pall Mall,Blend,Dunhill}:1}
state(3)={Swedish:Unknown{Blue,White,Green,Yellow}:Dog:Unknown{Coffee,Beer,Milk,Water}:Unknown{Blue Master,Blend,Dunhill}:Unknown{3,2,5,4}}
state(4)={British:Red:Unknown{Bird,Cat,Horse,Fish}:Unknown{Beer,Milk,Water}:Unknown{Blue Master,Pall Mall,Blend}:Unknown{3,2,5,4}}
========================================================
rule 04 applied: Green house is left neighbour of white house.
rule 05 applied: Green house host drinks coffee
step=2
state(0)={German:Unknown{Blue,White,Green}:Unknown{Cat,Horse,Fish}:Unknown{Coffee,Milk,Water}:Prince:Unknown{3,2,5,4}}
state(1)={Denmark:Unknown{Blue,White}:Unknown{Bird,Cat,Horse,Fish}:Tea:Unknown{Pall Mall,Blend,Dunhill}:Unknown{2,5,4}}
state(2)={Norwegian:Yellow:Unknown{Bird,Cat,Horse,Fish}:Unknown{Beer,Water}:Unknown{Blue Master,Pall Mall,Blend,Dunhill}:1}
state(3)={Swedish:Unknown{Blue,White,Green}:Dog:Unknown{Coffee,Beer,Milk,Water}:Unknown{Blue Master,Blend,Dunhill}:Unknown{3,2,5,4}}
state(4)={British:Red:Unknown{Bird,Cat,Horse,Fish}:Unknown{Beer,Milk,Water}:Unknown{Blue Master,Pall Mall,Blend}:Unknown{3,2,5,4}}
========================================================
step=3
state(0)={German:Unknown{Blue,White,Green}:Unknown{Cat,Horse,Fish}:Unknown{Coffee,Milk}:Prince:Unknown{3,2,5,4}}
state(1)={Denmark:Unknown{Blue,White}:Unknown{Bird,Cat,Horse,Fish}:Tea:Unknown{Pall Mall,Blend}:Unknown{2,5,4}}
state(2)={Norwegian:Yellow:Unknown{Bird,Cat,Horse,Fish}:Water:Dunhill:1}
state(3)={Swedish:Unknown{Blue,White,Green}:Dog:Unknown{Coffee,Beer,Milk}:Unknown{Blue Master,Blend}:Unknown{3,2,5,4}}
state(4)={British:Red:Unknown{Bird,Cat,Horse,Fish}:Unknown{Beer,Milk}:Unknown{Blue Master,Pall Mall,Blend}:Unknown{3,2,5,4}}
========================================================
step=4
state(0)={German:Unknown{White,Green}:Unknown{Cat,Fish}:Unknown{Coffee,Milk}:Prince:Unknown{3,2,5,4}}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Unknown{Cat,Fish}:Water:Dunhill:1}
state(3)={Swedish:Unknown{White,Green}:Dog:Unknown{Coffee,Beer,Milk}:Unknown{Blue Master}:Unknown{3,2,5,4}}
state(4)={British:Red:Unknown{Bird,Cat,Fish}:Unknown{Beer,Milk}:Unknown{Blue Master,Pall Mall}:Unknown{3,2,5,4}}
========================================================
step=5
state(0)={German:Unknown{White,Green}:Unknown{Cat}:Unknown{Coffee,Milk}:Prince:Unknown{3,5,4}}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Fish:Water:Dunhill:1}
state(3)={Swedish:Unknown{White,Green}:Dog:Unknown{Coffee,Beer,Milk}:Blue Master:Unknown{3,5,4}}
state(4)={British:Red:Unknown{Bird,Cat}:Unknown{Beer,Milk}:Pall Mall:Unknown{3,5,4}}
========================================================
step=6
state(0)={German:Unknown{White,Green}:Cat:Unknown{Coffee}:Prince:Unknown{3,5,4}}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Fish:Water:Dunhill:1}
state(3)={Swedish:Unknown{White,Green}:Dog:Beer:Blue Master:Unknown{3,5,4}}
state(4)={British:Red:Bird:Milk:Pall Mall:Unknown{3,5,4}}
========================================================
rule 05 applied: Green house host drinks coffee
step=7
state(0)={German:Unknown{Green}:Cat:Coffee:Prince:Unknown{5,4}}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Fish:Water:Dunhill:1}
state(3)={Swedish:White:Dog:Beer:Blue Master:Unknown{5,4}}
state(4)={British:Red:Bird:Milk:Pall Mall:3}
========================================================
step=8
state(0)={German:Green:Cat:Coffee:Prince:Unknown{5,4}}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Fish:Water:Dunhill:1}
state(3)={Swedish:White:Dog:Beer:Blue Master:Unknown{5,4}}
state(4)={British:Red:Bird:Milk:Pall Mall:3}
========================================================
step=9
state(0)={German:Green:Cat:Coffee:Prince:4}
state(1)={Denmark:Blue:Horse:Tea:Blend:2}
state(2)={Norwegian:Yellow:Fish:Water:Dunhill:1}
state(3)={Swedish:White:Dog:Beer:Blue Master:5}
state(4)={British:Red:Bird:Milk:Pall Mall:3}
========================================================
time taken=31

Process finished with exit code 0

 

The only thing I don't like is the puzzle class is too large, about <300 lines. I would've refactor it to <200 lines.