 # Advent of Code 2020 - Day 24

This topic is about Day 24 of the Advent of Code 2020 .

Thanks to @egze, we have a private leaderboard:

The join code is:
`39276-eeb74f9a`

Nice little puzzle to solve before breakfast. Here is my solution.

Nice one. Parsing with elixir is a breeze. I used 2D location instead of 3D and it works.

Haha ! Here is my solution. Instead of x,y,z I used only x,z (labelled q,r).

1 Like

I’m back after skipping a few days.

For part 1, figuring out how to represent the grid, was the most interesting. I also decided to keep it 2D, representing horizontally adjacent neighbours with an x-offset of 2. For the variable-length input, I’ve mostly been using multi-clause recursion for things like this so far, so this time I went with `Enum.chunk_while` - although ended up using a multi-clause anonymous function inside it anyway…

For part 2, it was basically the same as Day 17. I just updated that answer for the new coordinate system.

I borrowed day 17’s logic for part two, only to encounter > 8 mins runtime due to counting the number of black tiles and comparing it with the relevant range. I optimized that to an `Enum.reduce_while/3` so that I can return early once I’ve gone past the max, and shaved off a little more than 50% the time! I backported the change to day 17 and got a good 1/3 improvement (30s to 20s) too, not bad.

I guess where I differ from the solutions above is in the parsing (regex) and the looping (recursion).

``````defmodule AdventOfCode.Day24 do
@moduledoc "Day 24"

defp parse(line), do:
Enum.map(Regex.scan(~r/([ns]?[ew])/, line), fn [x, _] -> String.to_atom(x) end)

defp offset(:e, {x, y, z}), do: {x + 1, y - 1, z}
defp offset(:w, {x, y, z}), do: {x - 1, y + 1, z}
defp offset(:nw, {x, y, z}), do: {x, y + 1, z - 1}
defp offset(:ne, {x, y, z}), do: {x + 1, y, z - 1}
defp offset(:sw, {x, y, z}), do: {x - 1, y, z + 1}
defp offset(:se, {x, y, z}), do: {x, y - 1, z + 1}

defp shift(moves), do: Enum.reduce(moves, {0, 0, 0}, &offset/2)

defp map_tiles(input), do:
Enum.reduce(input, %{}, fn line, map -> Map.update(map, shift(parse(line)), true, &(!&1)) end)

def part1(input), do: Enum.count(map_tiles(input), fn {_, black?} -> black? end)

def nearby?({x1, y1, z1}, {x2, y2, z2}), do: abs(x1 - x2) <= 1 && abs(y1 - y2) <= 1 && abs(z1 - z2) <= 1

def black?(tiles, min..max), do: fn tile ->
# this was Enum.count(tiles, &(&1 != tile && nearby?(&1, tile))) in min..max
Enum.reduce_while(
tiles,
0,
fn x, acc ->
if x != tile && nearby?(x, tile),
do: {(if acc + 1 > max, do: :halt, else: :cont), acc + 1},
else: {:cont, acc}
end
) in min..max
end

defp expand(tiles), do:
MapSet.new(Enum.flat_map(tiles, fn tile -> Enum.map([:e, :w, :nw, :ne, :sw, :se], &(offset(&1, tile))) end))

defp cycle(tiles, 0), do: Enum.count(tiles)

defp cycle(tiles, i), do: cycle(
MapSet.new(Enum.filter(expand(tiles), black?(tiles, 2..2)) ++ Enum.filter(tiles, black?(tiles, 1..2))),
i - 1
)

def part2(input), do:
map_tiles(input)
|> Enum.reduce([], fn {tile, black?}, acc -> if black?, do: [tile | acc], else: acc end)
|> MapSet.new
|> cycle(100)
end
``````

What a nice day ! Nice and clean with Elixir.

In this journey I found this website : a nice and clean explanation on how to organize coordinates in an hexagonal system.

I did not enjoy part 2 at all.

When I worked on AoC 2017, day 11, I used Python, and I used complex numbers to represent directions –

``````C = {
"ne": complex(0.5, 0.5),
"n": complex(0, 1),
"nw": complex(-0.5, 0.5),
"se": complex(0.5, -0.5),
"s": complex(0, -1),
"sw": complex(-0.5, -0.5)
}
``````

Since complex numbers are a built-in type in Python, I could put a bunch of them in a list and call the `sum` function to add them up. In Elixir, I had to do a little more work.

``````defmodule Day24 do
@dirs %{
"w" => {0, -1},
"e" => {0, 1},
"nw" => {0.5, -0.5},
"ne" => {0.5, 0.5},
"se" => {-0.5, 0.5},
"sw" => {-0.5, -0.5}
}

|> String.split("\n", trim: true)
|> Enum.map(fn line ->
Regex.scan(~r/(se|ne|sw|nw|w|e)/, line, capture: :all_but_first)
|> List.flatten()
|> Enum.map(&Map.get(@dirs, &1))
end)
end

flip(input, %{{0, 0} => :white})
|> blacktiles()
end

def flip([], floor), do: floor

def flip([path | paths], floor) do
tile = Enum.reduce(path, {0, 0}, fn {ns, ew}, {nsacc, ewacc} -> {nsacc + ns, ewacc + ew} end)

case Map.get(floor, tile, :white) do
:black -> flip(paths, Map.put(floor, tile, :white))
:white -> flip(paths, Map.put(floor, tile, :black))
end
end

def blacktiles(floor) do
floor
|> Map.values()
|> Enum.count(&(&1 == :black))
end

flip(input, %{{0, 0} => :white})
|> dayflip(100)
|> blacktiles()
end

def dayflip(floor, 0), do: floor

def dayflip(floor, count) do
expand(floor)
|> Enum.reduce(%{}, fn {tile, color} = t, newfloor ->
blacks =
|> Enum.map(&Map.get(floor, &1))
|> Enum.count(&(&1 == :black))

cond do
color == :black and (blacks == 0 or blacks > 2) ->
Map.put(newfloor, tile, :white)

color == :white and blacks == 2 ->
Map.put(newfloor, tile, :black)

true ->
if t in floor, do: Map.put(newfloor, tile, color), else: newfloor
end
end)
|> dayflip(count - 1)
end

def expand(floor) do
floor
|> Enum.flat_map(fn {tile, _} -> adjtiles(tile) end)
|> MapSet.new()
|> Enum.reduce(%{}, fn tile, newfloor ->
Map.put(newfloor, tile, Map.get(floor, tile, :white))
end)
end

[
{ns + 0.5, ew + 0.5},
{ns + 0.5, ew - 0.5},
{ns - 0.5, ew + 0.5},
{ns - 0.5, ew - 0.5},
{ns, ew + 1},
{ns, ew - 1}
]
end
end
``````

After a day full of eating it was nice to end the day with todays puzzle.

Not being fluent in hexagonal but knowing I only needed to go relative to the center tile I decided to store the tiles in a 2D grid. I used this function to decide where to store them, so every second row was skewed to the right, ever second skewed to the left.

``````#           {-1, 1}   { 0, 1}
#      {-1, 0}   { 0, 0}   { 1, 0}
#           {-1,-1}   { 0,-1}
def get_coordinate({x, y}, dir) do
case dir do
:ne -> {x + rem(abs(y), 2), y + 1}
:se -> {x + rem(abs(y), 2), y - 1}
:nw -> {x - 1 + rem(abs(y), 2), y + 1}
:sw -> {x - 1 + rem(abs(y), 2), y - 1}
:e -> {x + 1, y}
:w -> {x - 1, y}
end
end
``````

The rest of the code

Hi there, I’m a bit late but here is my take on day 24.

It was quite easy in comparison of previous days, which caused me way more issues, damn crab! 