Posted in:

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    
Want to learn more about LINQ? Be sure to check out my Pluralsight course LINQ Best Practices.