Posted in:

In day 16’s Advent of Code challenge, we’re working out which of our many Aunt Sue’s sent us a present, based on a number of clues we have and a series of facts we know about each Aunt. Here’s how I solved it in C# and F#:

For my C# solution, I decided to use C# 6’s new dictionary initialiser syntax to set up my clues. The input parsing is fairly standard use of Regex and LINQ. Then for each Sue, we see if she is a candidate by checking if all the clues match.

Directory.SetCurrentDirectory(Path.GetDirectoryName(Util.CurrentQueryPath));

var clues = new Dictionary<string, int> {
    ["children"] = 3, ["cats"] = 7,    ["samoyeds"] = 2, ["pomeranians"] = 3, ["akitas"] = 0,
    ["vizslas"] = 0, ["goldfish"] = 5, ["trees"] = 3, ["cars"] = 2, ["perfumes"] = 1 };

var sues = File.ReadAllLines("day16.txt")
    .Select(r => Regex.Matches(r, @"(\w+)\: (\d+)")
                    .Cast<Match>()
                    .Select(m => m.Groups.Cast<Group>().Select(g=>g.Value).Skip(1).ToArray())
                    .ToDictionary(g => g[0],g => int.Parse(g[1])))
    .ToArray();

sues.Select((s, n) => new
{
    Sue = n + 1,
    Match = clues.All(kvp => !s.ContainsKey(kvp.Key) || s[kvp.Key] == kvp.Value)
}).Single(x => x.Match).Sue.Dump("a"); //213

sues.Select((s, n) => new
{
    Sue = n + 1,
    Match = clues.All(kvp =>
    !s.ContainsKey(kvp.Key) || 
    ((kvp.Key == "cats" || kvp.Key == "trees") ? s[kvp.Key] > kvp.Value :
    (kvp.Key == "pomeranians" || kvp.Key == "goldfish") ? s[kvp.Key] < kvp.Value :    
    s[kvp.Key] == kvp.Value))
}).Single(x => x.Match).Sue.Dump("b");

One trick I missed in C# was that the clues have more keys than the sues, so if you switch around the checking (“do the facts about this Sue match the clues?” instead of “do the clues match the facts about this Sue?”) then you don’t need to check if keys are present. We need to use Seq.forall which is the F# equivalent to LINQ’s All, and Array.findIndex turns out useful to get the number of the matching aunt Sue.

let parseFacts s = 
    [for m in Regex.Matches(s, @"(\w+)\: (\d+)") -> 
        [for g in m.Groups -> g.Value] |> Seq.skip 1 |> Seq.toArray]
    |> Seq.map (fun [|a;b|] -> (a, int b)) 

let sues = "day16.txt" |> File.ReadAllLines |> Array.map parseFacts 
let clues = parseFacts "children: 3, cats: 7, samoyeds: 2, pomeranians: 3, akitas: 0, vizslas: 0, goldfish: 5, trees: 3, cars: 2, perfumes: 1" |> Map.ofSeq

let f1 (t, n) = clues.[t] = n

let f2 (t, n) = 
    match t with
    | "cats" | "trees" -> n > clues.[t]
    | "pomeranians" |"goldfish" -> n < clues.[t]
    | _ -> n = clues.[t]

let find f = sues |> Array.findIndex (fun traits -> traits |> Seq.forall f) |> (+) 1

find f1 |> printfn "a: %d"
find f2 |> printfn "b: %d"
Want to learn more about LINQ? Be sure to check out my Pluralsight course LINQ Best Practices.