Aetherus
Advent of Code 2023 - Day 3
Don’t know why the regex ~r/[\W && [^\.]]/x does not work in Elixir. It works pretty well in Ruby.
Anyway, here is my solution:
Most Liked
seeplusplus
This one took me two hours continuously to get working.
contains_symbol = fn s ->
match = Regex.match?(~r/[^\d.]/, s)
match
end
grab_numbers = fn line ->
Regex.scan(~r/\d+/, line, return: :index)
|> Enum.map(fn [{index, len}] ->
{index, len, line |> String.slice(index, len) |> Integer.parse() |> elem(0)}
end)
end
part1 = fn s ->
for [pre, curr, post] <-
s
|> Enum.reject(&(String.length(&1) == 0))
|> then(fn x -> Enum.concat([""], x) end)
|> Enum.chunk(3, 1)
|> Enum.map(fn u -> Enum.map(u, &String.trim/1) end),
[{index, length}] <- Regex.scan(~r/\d+/, curr, return: :index),
contains_symbol.(String.slice(curr, max(index - 1, 0), length + 2)) ||
contains_symbol.(String.slice(pre, max(index - 1, 0), length + 2)) ||
contains_symbol.(String.slice(post, max(index - 1, 0), length + 2)),
{number, _} = String.slice(curr, index, length) |> Integer.parse() do
number
end
|> Enum.sum()
end
part2 = fn s ->
for [pre, curr, post] <-
s
|> Enum.reject(&(String.length(&1) == 0))
|> Enum.chunk(3, 1)
|> Enum.map(fn u -> Enum.map(u, &String.trim/1) end),
curr |> String.contains?("*"),
[{gear_idx, _}] <- Regex.scan(~r/\*/, curr, return: :index),
matches =
grab_numbers.(pre) |> Enum.concat(grab_numbers.(post)) |> Enum.concat(grab_numbers.(curr)),
valid_matches =
matches
|> Enum.filter(fn {m_idx, m_len, _} ->
gear_idx in max(0, m_idx - 1)..(m_idx + m_len)
end),
valid_matches |> Enum.count() == 2,
[{_, _, a}, {_, _, b}] = valid_matches do
a * b
end
|> Enum.sum()
end
part1.(IO.stream()) |> IO.puts()
stevensonmt
I have not yet looked at the solutions in this thread but I just wanted to state that for day 3 this was a very difficult challenge relative to previous years. I think there may have been an effort to handicap the AI coders with the difficult input parsing this year.
al2o3cr
+1 for this - it’s my favorite part of AoC.
With “real-world” problems, it usually takes months or years to fully regret an architectural decision - with an AoC problem, all it takes is part 2 ![]()
lud
I like it, it’s very concise.
Mine is quite long, and this does not even contains the code for the Grid helper ![]()
defmodule AdventOfCode.Y23.Day3 do
alias AoC.Input, warn: false
def read_file(file, _part) do
Input.stream!(file, trim: true)
end
def parse_input(input, _part) do
AoC.Grid.parse_stream(input, &parse_char/1)
end
defp parse_char(<<n>>) when n in ?0..?9, do: {:ok, n - ?0}
defp parse_char(<<?.>>), do: :ignore
defp parse_char(<<?*>>), do: {:ok, :gear}
defp parse_char(<<_>>), do: {:ok, :sym}
def part_one(grid) do
grid
|> Enum.filter(fn {xy, val} -> is_integer(val) and sym_neighbour?(grid, xy) end)
|> Enum.map(fn {xy, _} -> first_digit(grid, xy) end)
|> Enum.uniq()
|> Enum.map(&collect_number(grid, &1))
|> Enum.sum()
end
defp sym_neighbour?(grid, xy) do
xy |> AoC.Grid.cardinal8() |> Enum.any?(fn txy -> Map.get(grid, txy) in [:sym, :gear] end)
end
defp first_digit(grid, xy) do
west_xy = AoC.Grid.translate(xy, :w)
case Map.get(grid, west_xy) do
n when is_integer(n) -> first_digit(grid, west_xy)
_ -> xy
end
end
defp collect_number(grid, xy) do
collect_number(grid, xy, [Map.fetch!(grid, xy)])
end
defp collect_number(grid, xy, acc) do
east_xy = AoC.Grid.translate(xy, :e)
case Map.get(grid, east_xy) do
n when is_integer(n) -> collect_number(grid, east_xy, [n | acc])
_ -> acc |> :lists.reverse() |> Integer.undigits()
end
end
# -- Part 2 -----------------------------------------------------------------
def part_two(grid) do
grid
# For each digit in the grid, associate the digit XY with the neighbouring
# gear XY, keeping only digits with such neighbour.
|> Enum.flat_map(fn
{xy, val} when is_integer(val) ->
case gear_neighbour(grid, xy) do
nil -> []
gear_xy -> [{gear_xy, xy}]
end
_ ->
[]
end)
# Group those digits XY by their neighbouring gear XY.
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
# Transform the digits XY into the first digit XY of their number
|> Enum.flat_map(fn {_gear_xy, digits_xys} ->
first_digits = digits_xys |> Enum.map(&first_digit(grid, &1)) |> Enum.uniq()
# Keep only the gear groups with exactly two numbers. Collect the actual
# numbers and compute the product.
case first_digits do
[digit_a_xy, digit_b_xy] ->
num_a = collect_number(grid, digit_a_xy)
num_b = collect_number(grid, digit_b_xy)
[num_a * num_b]
_ ->
[]
end
end)
|> Enum.reduce(&Kernel.+/2)
end
defp gear_neighbour(grid, xy) do
xy |> AoC.Grid.cardinal8() |> Enum.find(fn txy -> Map.get(grid, txy) == :gear end)
end
end
But hey, it works
Part 2 is solved in 5ms which I am not very satisfied with.
code-shoily
Wow this one. Not a dataset I’d like to traverse at midnight. But I love LiveBook. I really do.
defmodule AdventOfCode.Y2023.Day03 do
alias AdventOfCode.Helpers.{InputReader, Transformers}
def input, do: InputReader.read_from_file(2023, 3)
def run(input \\ input()) do
input = parse(input)
{run_1(input), run_2(input)}
end
defp run_1(input) do
input
|> Enum.map(fn {v, _, _} -> String.to_integer(v) end)
|> Enum.sum()
end
defp run_2(input) do
input
|> Enum.map(fn {v, _, gear} -> {String.to_integer(v), Enum.uniq(gear)} end)
|> Enum.flat_map(fn {num, gears} ->
gears
|> Enum.map(fn gear ->
{gear, num}
end)
end)
|> Enum.group_by(fn {a, _} -> a end, fn {_, b} -> b end)
|> Map.filter(fn {_, v} -> length(v) == 2 end)
|> Map.values()
|> Enum.map(fn [a, b] -> a * b end)
|> Enum.sum()
end
def parse(data \\ input()) do
grid = data |> Transformers.lines() |> Enum.map(&String.graphemes/1) |> Transformers.grid2d()
0..(grid |> Map.keys() |> Enum.max() |> elem(0))
|> Enum.flat_map(&collect_all(grid, &1))
|> Enum.filter(fn {_, n, _} -> n == true end)
end
defp dirs(x, y) do
[
{x + 1, y},
{x - 1, y},
{x, y + 1},
{x, y - 1},
{x + 1, y + 1},
{x - 1, y - 1},
{x + 1, y - 1},
{x - 1, y + 1}
]
end
@reject MapSet.new(~w/. 1 2 3 4 5 6 7 8 9 0/)
defp is_part(grid, {x, y}) do
dirs(x, y)
|> Enum.map(&grid[&1])
|> Enum.reject(&(is_nil(&1) || MapSet.member?(@reject, &1)))
|> Enum.empty?()
|> Kernel.not()
end
defp get_gears(grid, {x, y}), do: Enum.filter(dirs(x, y), &(grid[&1] == "*"))
defp collect_one(grid, pos), do: collect_one(grid[pos], grid, pos, "", false, [])
defp collect_one(cur, grid, {x, y}, digits, part?, gears) when cur in ~w/1 2 3 4 5 6 7 8 9 0/ do
collect_one(
grid[{x, y + 1}],
grid,
{x, y + 1},
digits <> cur,
part? || is_part(grid, {x, y}),
get_gears(grid, {x, y}) ++ gears
)
end
defp collect_one(nil, _, {_, _}, digits, part?, gears), do: {:halt, digits, part?, gears}
defp collect_one(_, _, {_, y}, digits, part?, gears), do: {{:cont, y}, digits, part?, gears}
defp collect_all(grid, row), do: collect_all(grid, row, 0, [])
defp collect_all(grid, row, col, numbers) do
case collect_one(grid, {row, col}) do
{:halt, "", _, _} ->
numbers
{_, "", _, _} ->
collect_all(grid, row, col + 1, numbers)
{:halt, number, part?, gears} ->
[{number, part?, gears} | numbers]
{{:cont, next}, number, part?, gears} ->
collect_all(grid, row, next, [{number, part?, gears} | numbers])
end
end
end







