Advent of Code Day 6 Solved in C# and F#
December 10. 2015 Posted in:
So I’m still just about managing to keep up with the Advent of Code challenges. Here’s me talking through my solution to day 6’s problem:
Here’s my solution to part a done in three stages in C#
var instructions = File.ReadAllLines("day6.txt");
var pattern = @"(turn on|toggle|turn off)\ (\d+)\,(\d+)\ through\ (\d+)\,(\d+)";
var instructionsFlattened = instructions
.Select(i => Regex.Match(i, pattern).Groups)
.Select(g => new
{
Action = g[1].Value,
From = new {
X = int.Parse(g[2].Value),
Y = int.Parse(g[3].Value) },
To = new {
X = int.Parse(g[4].Value),
Y = int.Parse(g[5].Value) }
})
.SelectMany(i =>
from x in Enumerable.Range(i.From.X, 1 + i.To.X - i.From.X)
from y in Enumerable.Range(i.From.Y, 1 + i.To.Y - i.From.Y)
select new { i.Action, x, y });
// apply the instructions
var lights = new bool[1000, 1000];
foreach (var i in instructionsFlattened)
{
if (i.Action == "turn on")
lights[i.x, i.y] = true;
else if (i.Action == "turn off")
lights[i.x, i.y] = false;
else if (i.Action == "toggle")
lights[i.x, i.y] = !lights[i.x, i.y];
}
// count the lights on
(from x in Enumerable.Range(0, 1000)
from y in Enumerable.Range(0, 1000)
where lights[x, y]
select 1).Sum().Dump("lights on");
But I decided I wanted to solve this in a single LINQ expression, and this is possible using LINQ’s Aggregate method, so here’s part b solved in C# with Aggregate
File.ReadAllLines("day6.txt")
.Select(i => Regex.Match(i,
@"(turn on|toggle|turn off)\ (\d+)\,(\d+)\ through\ (\d+)\,(\d+)").Groups)
.Select(g => new
{
Action = g[1].Value,
From = new { X = int.Parse(g[2].Value), Y = int.Parse(g[3].Value) },
To = new { X = int.Parse(g[4].Value), Y = int.Parse(g[5].Value) }
})
.SelectMany(i =>
from x in Enumerable.Range(i.From.X, 1 + i.To.X - i.From.X)
from y in Enumerable.Range(i.From.Y, 1 + i.To.Y - i.From.Y)
select new { i.Action, x, y })
.Aggregate(new int[1000,1000], (acc, next) => {
var brightness = acc[next.x,next.y];
if (next.Action == "turn on")
brightness+=1;
else if (next.Action == "turn off")
brightness = Math.Max(0, brightness - 1);
else if (next.Action == "toggle")
brightness += 2;
acc[next.x, next.y] = brightness;
return acc;
})
.Cast<int>() // flattens a multi-dimensional array
.Sum()
// brightness is 14687245
And finally of course, as part of my on-going bid to improve my F# skills, I made an F# version of this solution:
let instructions = File.ReadAllLines("day6.txt")
let turnOn n = n + 1
let turnOff n = max (n - 1) 0
let toggle n = n + 2
let selectAction = function
| "turn on" -> turnOn
| "turn off" -> turnOff
| "toggle" -> toggle
let parseInstruction actionSelector i =
let pattern = @"(turn on|toggle|turn off)\ (\d+)\,(\d+)\ through\ (\d+)\,(\d+)"
let groups = Regex.Match(i, pattern).Groups
let action = actionSelector groups.[1].Value
let fromPos = (int groups.[2].Value), (int groups.[3].Value)
let toPos = (int groups.[4].Value), (int groups.[5].Value)
(action, fromPos, toPos)
let expandPositions (x0,y0) (x1,y1) =
seq { for x in x0 .. x1 do
for y in y0 .. y1 do
yield (x,y) }
let applyAction (acc:int[,]) (action,(x,y)) =
acc.[x,y] <- action acc.[x,y]
acc
let calculate actionSelector (input:string[]) =
let startState = Array2D.create 1000 1000 0
input
|> Seq.map (parseInstruction actionSelector)
|> Seq.collect (fun (action,fromPos,toPos) ->
seq { for pos in (expandPositions fromPos toPos) do
yield (action,pos) })
|> Seq.fold applyAction startState
|> Seq.cast<int> // same trick works to flatten 2d array
|> Seq.sum
calculate selectAction instructions |> Dump
let turnOnA n = 1
let turnOffA n = 0
let toggleA n = if n = 0 then 1 else 0
let selectActionA = function
| "turn on" -> turnOnA
| "turn off" -> turnOffA
| "toggle" -> toggleA
calculate selectActionA instructions |> Dump
As always, comments on ways I could improve my solutions are very welcome.
Want to learn more about LINQ? Be sure to check out my Pluralsight course LINQ Best Practices.
Comments
Continuing in my regex active pattern madness here is what I've done :
Sehnsuchtlooks great, and more bits of F# I've not got to grips with yet - the backwards pipe, and using Maps. This is also helping me see how useful active patterns can be. I've read about them, but haven't used them myself yet. Will have to make it a goal to use them before the end of AoC
Mark Heath