Advent of Code Day 18–Game of Lights
The day 18 Advent of Code challenge basically requires us to create the Conway’s Game of Life algorithm and run it repeatedly on a 100x100 grid. See how I tackled this problem in C# and F# in this video:
I won’t post both parts of my C# solution as they were very similar, but here’s what I cam up with for part b. It probably could do with a bit of refactoring, but sadly time didn’t permit today:
var start = File.ReadAllLines("day18.txt");
var repetitions = 100;
var state = start.Select(s => s.Trim().Select(c => c == '#' ? 1 : 0).ToArray()).ToArray();
Func<int, int, int> getLight = (x, y) =>
{
if (x < 0 || x >= state.Length) return 0;
if (y < 0 || y >= state[x].Length) return 0;
if (x == 0 && y == 0) return 1;
if (x == state.Length - 1 && y == 0) return 1;
if (x == 0 && y == state[x].Length - 1) return 1;
if (x == state.Length - 1 && y == state[x].Length - 1) return 1;
return state[x][y];
};
Func<int, int, int> getNeighbourSum = (x, y) => getLight(x - 1, y - 1) + getLight(x, y - 1) + getLight(x + 1, y - 1) +
getLight(x - 1, y) + getLight(x + 1, y) +
getLight(x - 1, y + 1) + getLight(x, y + 1) + getLight(x + 1, y + 1);
Func<int, int, int> getNextValue = (x, y) =>
{
if (x == 0 && y == 0) return 1;
if (x == state.Length - 1 && y == 0) return 1;
if (x == 0 && y == state[x].Length - 1) return 1;
if (x == state.Length - 1 && y == state[x].Length - 1) return 1;
return getNeighbourSum(x, y) == 3 ? 1 :
(getNeighbourSum(x, y) == 2 && getLight(x, y) == 1) ? 1 : 0;
};
for (int a = 0; a < repetitions; a++)
{
var nextState = Enumerable.Range(0, state.Length)
.Select(x => Enumerable.Range(0, state[x].Length)
.Select(y => getNextValue(x, y)).ToArray()).ToArray();
state = nextState;
}
state.Sum(row => row.Sum()).Dump(); // 924
My F# version is similar except I did make the effort to write one bit of code to solve both parts of the solution, and I left the state as simply an array of strings. To avoid any use of mutable variables, I used Seq.fold
to repeatedly run the animate function to return the next state. I can’t help thinking there must be a nicer way to do this. Also, my F# solution performs quite poorly for part b, so could do with some optimisation (I think my cornersOn
method is probably a significant contributing factor).
let isOn (state:string[]) isStuckOn (x,y) =
match x,y with
| _,_ when x < 0 || y < 0 || x >= state.Length || y >= state.[x].Length -> false
| _,_ when isStuckOn (x,y) -> true
| _ -> state.[x].[y] = '#'
let getNeighbourSum (state:string[]) isStuckOn (x,y) =
[(-1,-1);(0,-1);(1,-1);(-1,0);(1,0);(-1,1);(0,1);(1,1)]
|> Seq.map (fun (a,b) -> (x+a,y+b))
|> Seq.filter (isOn state isStuckOn)
|> Seq.length
let getNextValue (state:string[]) isStuckOn (x,y) =
if isStuckOn (x,y) then '#'
else
match getNeighbourSum state isStuckOn (x,y) with
| 3 -> '#'
| 2 -> if isOn state isStuckOn (x,y) then '#' else '.'
| _ -> '.'
let animate (state:string[]) isStuckOn =
[|for x in 0..state.Length-1 ->
new System.String [|for y in 0..state.[x].Length-1 -> getNextValue state isStuckOn (x,y)|] |]
let countLights (state:string[]) =
state |> Seq.map (fun r -> r .Replace(".","").Length) |> Seq.sum
let animated state n isStuckOn = [1..n] |> Seq.fold (fun s _ -> animate s isStuckOn) state
let startState = "day18.txt" |> File.ReadAllLines
let testState = [|".#.#.#";"...##.";"#....#";"..#...";"#.#..#";"####.."|]
let cornersOn (x,y) = List.exists ((=) (x,y)) [(0,0);(0,99);(99,0);(99,99)]
animated startState 100 (fun (x,y)->false) |> countLights |> printfn "a: %d" // a: 814
animated startState 100 cornersOn |> countLights |> printfn "b: %d" // b: 924