Skip to content

Instantly share code, notes, and snippets.

@akash-akya
Created December 17, 2020 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akash-akya/bb788d5900f134fda8755867683d2f0f to your computer and use it in GitHub Desktop.
Save akash-akya/bb788d5900f134fda8755867683d2f0f to your computer and use it in GitHub Desktop.
Advent of Code 2020: Day 17
defmodule Advent2020.Day17 do
def input, do: File.read!(Path.expand("day17.txt", :code.priv_dir(:advent_2020)))
def sample_input do
"""
.#.
..#
###
"""
end
def parse(input) do
String.split(input, "\n", trim: true)
|> Enum.with_index()
|> Enum.flat_map(fn {line, y} ->
String.codepoints(line)
|> Enum.with_index()
|> Enum.filter(fn {v, _} -> v == "#" end)
# NOTE: using co-ordinates in reverse order
|> Enum.map(fn {_, x} -> [y, x] end)
end)
end
# set remaining co-ordinates to 0
def init_state(grid, 2), do: MapSet.new(grid)
def init_state(grid, dimen) do
Enum.map(grid, fn pos -> [0 | pos] end)
|> init_state(dimen - 1)
end
# returns min & max for each dimension
defp min_max([dimen | rest], nil), do: [{dimen, dimen} | min_max(rest, nil)]
defp min_max([], _), do: []
defp min_max([dimen | rest], [{a, b} | acc]) do
[{min(a, dimen), max(b, dimen)} | min_max(rest, acc)]
end
defp grid_limits(grid) do
MapSet.to_list(grid)
|> Enum.reduce(nil, &min_max(&1, &2))
end
defp iterate([], pos, acc, func), do: func.(pos, acc)
defp iterate([first..last | rest], cur, acc, func) do
Enum.reduce(first..last, acc, fn pos, acc ->
iterate(rest, [pos | cur], acc, func)
end)
end
@doc """
Takes list of {min, max} specifying the range for each
dimension and apply `func` with accumulated position.
Function `func` takes position as first argument and
acc as second argument, similar to reduce
```elixir
for_ranges(
[-1..1, 2..4],
[],
fn [a, b], acc ->
[{a, b} | acc]
end
)
```
"""
defp for_ranges(list_of_range, acc, func),
do: iterate(Enum.reverse(list_of_range), [], acc, func)
defp activated_neighbours(grid, pos) do
for_ranges(
Enum.map(pos, fn d -> (d - 1)..(d + 1) end),
0,
fn
^pos, count ->
count
pos, count ->
if MapSet.member?(grid, pos) do
count + 1
else
count
end
end
)
end
defp next_cycle(grid) do
for_ranges(
Enum.map(grid_limits(grid), fn {a, b} -> (a - 1)..(b + 1) end),
MapSet.new(),
fn
pos, acc ->
case {MapSet.member?(grid, pos), activated_neighbours(grid, pos)} do
{false, count} when count == 3 ->
MapSet.put(acc, pos)
{true, count} when count == 2 or count == 3 ->
MapSet.put(acc, pos)
_ ->
acc
end
end
)
end
defp run(dimen) do
grid = input() |> parse() |> init_state(dimen)
Enum.reduce(1..6, grid, fn _, grid -> next_cycle(grid) end)
|> Enum.count()
end
def part_one, do: run(3)
def part_two, do: run(4)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment