Day 3
Mix.install([:kino])
Get input
input =
Kino.Input.textarea("Paste input here",
default: """
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
"""
)
defmodule Helper do
def is_numeric?(string) when is_binary(string) do
String.length(string) == 1 &&
Enum.any?(?0..?9, fn x -> x == String.to_charlist(string) |> hd() end)
end
def get_neighbors(number, {x1, y1}) do
end_x = x1 + String.length(number) - 1
([{x1 - 1, y1 - 1}, {x1 - 1, y1}, {x1 - 1, y1 + 1}] ++
Enum.flat_map(x1..end_x, fn x -> [{x, y1 - 1}, {x, y1 + 1}] end) ++
[{end_x + 1, y1 - 1}, {end_x + 1, y1}, {end_x + 1, y1 + 1}])
|> Enum.filter(fn {x, y} -> x >= 0 and y >= 0 end)
end
def adjacent_to_symbol?(schematic_lookup, number, start_pos) do
neighbors = get_neighbors(number, start_pos)
Enum.any?(neighbors, fn {x, y} ->
char = Map.get(schematic_lookup, {x, y}, ".")
if is_numeric?(char) do
raise "neighbor at (#{x},#{y}) unexpectedly was numeric: #{char}"
end
!is_numeric?(char) and char !== "."
end)
end
end
schematic =
input
|> Kino.Input.read()
|> String.split("\n", trim: true)
|> Stream.map(&String.split(&1, "", trim: true))
schematic_lookup =
schematic
|> Stream.with_index()
|> Stream.flat_map(fn {row, y} ->
row |> Stream.with_index() |> Stream.map(fn {char, x} -> {{x, y}, char} end)
end)
|> Enum.into(%{})
part_nums =
schematic
|> Stream.map(fn arr -> arr ++ ["."] end)
|> Stream.with_index()
|> Stream.map(fn {row, y} ->
# collect each full number along with its start position in the grid
row
|> Stream.with_index()
|> Enum.reduce({"", []}, fn {char, x}, {current_n, part_numbers} ->
cond do
Helper.is_numeric?(char) ->
{current_n <> char, part_numbers}
String.length(current_n) === 0 ->
{"", part_numbers}
true ->
{"", part_numbers ++ [{current_n, {x - String.length(current_n), y}}]}
end
end)
end)
|> Enum.flat_map(fn {_, n} -> n end)
Part 1
part_nums
|> Enum.filter(fn {n, start_pos} ->
Helper.adjacent_to_symbol?(schematic_lookup, n, start_pos)
end)
|> Enum.map(fn {n, _c} -> String.to_integer(n) end)
|> Enum.sum()
Part 2
gears =
schematic_lookup
|> Enum.filter(fn
{_, "*"} -> true
_ -> false
end)
|> Enum.map(fn {coords, _} -> coords end)
|> MapSet.new()
part_nums
|> Enum.reduce(%{}, fn {n, start_pos}, acc ->
gear_pos = Helper.get_neighbors(n, start_pos)
case Enum.find(gear_pos, &MapSet.member?(gears, &1)) do
nil -> acc
p -> Map.update(acc, p, [n], &(&1 ++ [n]))
end
end)
|> Stream.filter(fn
{_, [_, _]} -> true
_ -> false
end)
|> Stream.map(fn {_, ns} -> Stream.map(ns, &String.to_integer/1) |> Enum.product() end)
|> Enum.sum()