Posted in:

Day 10’s challenge at Advent of Code was based on a fun number sequence called “Look and Say” numbers. As usual, I recorded a video explaining my solution, but inexplicably, YouTube rejected my video, citing “inappropriate content”! I’ve really got no idea what the issue with it is, and I’ve appealed the decision, so hopefully at some point I’ll be able to update my playlist with the video.

But for now, my workaround is to upload to Vimeo. So if you want to watch the content that was too shocking for YouTube to host, here it is…

Anyway, for C#, the problem is trivially solved with MoreLINQ’s GroupAdjacent method, and once again the Aggregate method comes in handy to construct each consecutive member of the sequence based on the last one.

Enumerable.Range(1, 40)
    .Aggregate("1113122113".Select(c => c - '0').ToArray(),
       (acc,_) => acc
    .GroupAdjacent(n => n)
    .SelectMany(g => new int[] { g.Count(), g.First() })
    .ToArray())
    .Count()

For my F# solution I created my own CountAdjacent method, to return tuples of adjacent elements and their counts. Initially I did this with some mutable state (as shown in the video), but I’ve had time to clean it up a bit since then, and might even record a new video to see if I can get it onto YouTube.

So here’s  what I ended up with in F#, making use of Seq.fold both in implementing CountAdjacent, and to repeatedly run the output of lookAndSay on its own output.

let input = "1113122113" |> Seq.map (fun f -> int f - int '0') |> Seq.toArray

let countAdjacent = 
    Seq.fold (fun s x -> 
        match s with
        | [|n;c|]::tail when c = x -> [|n+1;c|]::tail
        | l -> [|1;x|]::l) []
    >> List.rev

let lookAndSay = countAdjacent >> Seq.collect id >> Seq.toArray

let getLengthAfterRepetitions repetitions = 
    [1..repetitions]
    |> Seq.fold (fun acc _ -> lookAndSay acc) input 
    |> Seq.length

getLengthAfterRepetitions 40 |> printf "a: %d"
getLengthAfterRepetitions 50 |> printf "b: %d"

As always let me know in the comments how I could improve my solution.

Want to learn more about LINQ? Be sure to check out my Pluralsight course LINQ Best Practices.

Comments

Comment by Sehnsucht

This time the outer list of my active pattern will be put at use

let (|Matches|) pattern =
let rgx = Regex (pattern, RegexOptions.Compiled)
fun input -> [for m in rgx.Matches input -> [for g in m.Groups -> g.Value]]

let common nth =
"3113322113"
|> Seq.unfold (fun str ->
match str with
Matches "(\d)\1*" xs ->
Some (str, xs
|> Seq.map (function [repeat; digit] -> sprintf "%d%s" repeat.Length digit
| _ -> failwith "")
|> String.concat ""))
|> Seq.item nth
|> String.length

let part1 () = common 40
let part2 () = common 50

Sehnsucht
Comment by Mark Heath

of course! this one can be solved with regexs too. Nice solution and I actually understand this one :)

Mark Heath