This topic is about Day 24 of the Advent of Code 2020 .
Thanks to @egze, we have a private leaderboard:
https://adventofcode.com/2020/leaderboard/private/view/39276
The join code is:
39276-eeb74f9a
This topic is about Day 24 of the Advent of Code 2020 .
Thanks to @egze, we have a private leaderboard:
https://adventofcode.com/2020/leaderboard/private/view/39276
The join code is:
39276-eeb74f9a
Nice one. Parsing with elixir is a breeze. I used 2D location instead of 3D and it works.
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}
}
def readinput() do
File.read!("24.input.txt")
|> 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
def part1(input \\ readinput()) do
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
def part2(input \\ readinput()) do
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 =
adjtiles(tile)
|> 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
def adjtiles({ns, ew}) do
[
{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