In part 1, I described a riddle Einstein had come up with, and a proposed algorithm for programmatically finding the solution. In this post, we will take the next steps of writing our F# program, including defining the domain objects, creating a list of all the possibilities of homes, and begin filtering down this list to find the answer.
First I wanted to define a set of the distinct types for each of the houses properties as described in the riddle, e.g. colors, nationalities, beverages, smokes, and pets. An enum seemed like the most natural fit:
type ColorHouse = Red = 1 | Green = 2 | White = 3 | Yellow = 4 | Blue = 5
This was my first conceptual change from the C# world. I had originally (unintentionally) created a discriminated union such as this…
type ColorHouse = Red | Green | White | Yellow | Blue //discriminated union
So what’s the difference between an enum and discriminated union? You can read about discriminated unions here as well as some of the differences with enums here. For our purposes we need something easy to enumerate over, which .NET provides many utilities for enumerating over enums but not discriminated unions.
Next, I defined a House type with members for each of our enum types. This is a little verbose, and there may be more concise way to define a simple type in F# but it suits our purposes for now:
type House(number:int, color:ColorHouse, nationality: Nationality, beverages:Beverages, smoke:Smoke, pet:Pet) =
member this.Number = number
member this.Color = color
member this.Nationality = nationality
member this.Beverages = beverages
member this.Smoke = smoke
member this.Pet = pet
Next, we iterate through each of the available values of each property type, and create a home with a distinct set of property values. Now we have created a set homes with all combinations of property values represented.
let houses = [for i in [1..5] do
for color in colors do
for nationality in nationalities do
for beverage in beverages do
for smoke in smokes do
for pet in pets do
yield new House(i, color, nationality, beverage, smoke, pet)]
Now that we have all possibilities of house values (15625!) we are going to filter out the houses that do not pass the rules applicable to a single home given to us in the riddle. Lets create the set of functions that determine a valid home…
let rule1(house:House) = exnor((house.Nationality = Nationality.Brit), (house.Color = ColorHouse.Red))
let rule10(house:House) = exnor((house.Nationality = Nationality.German), (house.Smoke = Smoke.Prince))
Great, now lets create a list of those rules and apply them to all the house combinations.
let singleRuleSet = [rule1;rule2;rule3;rule4;rule5;rule6;rule7;rule8;rule9;rule10;]
let rulesPredicate(house:House) = singleRuleSet |> List.forall(fun rule -> rule(house))
let housesPassedRules = houses |> List.filter rulesPredicate
We now have the set of homes that pass the ‘single home’ rules. For the final part of this blog series, we will create sets of sets of homes and a list of ‘multi home’ rules, and apply this last rule filtering. This should leave a single set of 5 homes, and reveal the answer to the riddle!
To be continued…part 3.
Download the source code for this post.