Here’s my day 3 code
This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata but looks like it wasn’t the case. Also, nice to have that Toboggan reference. I love cross-overs.
Here’s my day 3 code
This was quite easy. I was afraid Part 2 would be “un-regex-able” and was preparing for hand crafting automata but looks like it wasn’t the case. Also, nice to have that Toboggan reference. I love cross-overs.
Part 1:
defmodule Part1 do
def scan(input) do
Regex.scan(~r/mul\((\d{1,3}),(\d{1,3})\)/, input, capture: :all_but_first)
|> Enum.reduce(0, fn [a, b], acc ->
acc + String.to_integer(a) * String.to_integer(b)
end)
end
end
Part 2:
defmodule Part2 do
def scan(input, enabled \\ :enabled, acc \\ 0)
def scan(input, :enabled, acc) do
case Regex.split(~r/don't\(\)/, input, parts: 2) do
[valid, rest] -> scan(rest, :disabled, acc + Part1.scan(valid))
[valid] -> acc + Part1.scan(valid)
end
end
def scan(input, :disabled, acc) do
case Regex.split(~r/do\(\)/, input, parts: 2) do
[_invalid, rest] -> scan(rest, :enabled, acc)
[_invalid] -> acc
end
end
end
Here are my solutions for today. (if another thread for this pops up, I’ll try to delete it. My version of this thread is still pending approval )
Part 1:
defmodule Day3.Part1 do
def solve() do
File.stream!("03/input.txt")
|> Enum.reduce(
0,
fn line, acc ->
acc +
(~r/mul\((\d{1,3}),(\d{1,3})\)/
|> Regex.scan(line |> String.trim(), capture: :all_but_first)
|> Enum.reduce(
0,
fn [a, b], acc -> acc + String.to_integer(a) * String.to_integer(b) end
))
end
)
|> IO.puts()
end
end
Day3.Part1.solve()
Part 2:
defmodule Day3.Part2 do
defp sum_part(part) do
~r/mul\((\d{1,3}),(\d{1,3})\)/
|> Regex.scan(part, capture: :all_but_first)
|> Enum.reduce(
0,
fn [a, b], acc -> acc + String.to_integer(a) * String.to_integer(b) end
)
end
def solve() do
File.stream!("03/input.txt")
|> Enum.reduce(
{0, true},
fn line, {acc, start_enabled?} ->
{acc +
(line
|> String.trim()
|> String.split("do")
|> (fn parts ->
if start_enabled? do
parts
else
parts
|> Enum.drop(1)
end
end).()
|> Enum.filter(&(not String.starts_with?(&1, "n't()")))
|> Enum.map(&sum_part/1)
|> Enum.sum()),
~r/do\(\)/
|> Regex.scan(line, capture: :all, return: :index)
|> List.last()
|> List.last()
|> elem(0) >
~r/don't\(\)/
|> Regex.scan(line, capture: :all, return: :index)
|> List.last()
|> List.last()
|> elem(0)}
end
)
|> elem(0)
|> IO.puts()
end
end
Day3.Part2.solve()
I haven’t used regex in a while - made a few dumb mistakes before getting it right, lol.
Thanks for your regex. It’s the first time I saw (?>pattern)
in a regex.
Here’s my solution:
Part 1
~r/
(?<=mul\() # prefixed by "mul(" (positive lookbehind, not captured)
(\d{1,3}) # max-3-digit number (captured)
, # matches a comma (not captured)
(\d{1,3}) # max-3-digit number (captured)
(?=\)) # suffixed by ")" (positive lookahead, not captured)
/x
|> Regex.scan(puzzle_input, capture: :all_but_first)
|> List.flatten()
|> Enum.map(&String.to_integer/1)
|> Enum.chunk_every(2)
|> Enum.map(fn [a, b] -> a * b end)
|> Enum.sum()
Part 2
~r/
(do\(\)) # matches "do()" (captured)
| # or
(don't\(\)) # matches "don't()" (captured)
| # or
(?<=mul\()(\d{1,3}),(\d{1,3})(?=\)) # matches the same pattern as in Part 1, captures only the numbers
/x
|> Regex.scan(puzzle_input, capture: :all_but_first)
|> Enum.map(fn
["do()"] -> :on
["", "don't()"] -> :off
["", "", a, b] -> {String.to_integer(a), String.to_integer(b)}
end)
|> Enum.reduce({0, :on}, fn
:on, {sum, _} -> {sum, :on}
:off, {sum, _} -> {sum, :off}
{a, b}, {sum, :on} -> {sum + a * b, :on}
_, acc -> acc
end)
|> elem(0)
Hey!
This is my solution for Day 03.
Regex.scan(~r/mul\((\d+),(\d+)\)/, puzzle_input, capture: :all_but_first)
|> Enum.map(fn [a, b] ->
String.to_integer(a) * String.to_integer(b)
end)
|> Enum.sum()
Regex.scan(~r/mul\((-?\d+),(-?\d+)\)|do(?:n't)?\(\)/, puzzle_input)
|> Enum.reduce({0, :enabled}, fn
["don't()"], {count, _status} ->
{count, :disabled}
["do()"], {count, _status} ->
{count, :enabled}
[_text, a, b], {count, :enabled} ->
result = String.to_integer(a) * String.to_integer(b)
{result + count, :enabled}
[_text, _a, _b], {count, :disabled} ->
{count, :disabled}
end)
|> elem(0)
I just realized I could just have one function with regex as an additional parameter. (In case of first, adding true on both cases unlocks the whole list for processing)
I love it. Saves me a lot of “emptiness” lol.
I went for a solution using regexes and regretted it almost immediately. It took me a while to realize that Regex.run/3
with the :global
option will not return multiple solutions (as :re.run/3
would), but that I needed to use Regex.scan/3
. Fortunately, having invested a lot of time solving part 1 with a regex, it turned out it was possible to solve also part 2 with a regex.
Having tried regexes, I decided to implement a solution in Erlang using the binary syntax to do the parsing:
UPDATE: After looking at the other solutions, I realized that I only looked for do
and don't
instead of do()
and don't()
. That happened to produce the correct result (at least for my input), but I’ve now updated my programs to match the parens too.
This is the way.
Just some average-ugly regexes.
More regexes.
I’m not sure why do
produces ["", "", "do()"]
and don't
produces ["", "", "", "don't()"]
. The number of empty strings is consistent, but my regex-fu is not enough to figure out why / prevent it.
def part1(input) do
Regex.scan(~r/mul\((\d+),(\d+)\)/, input, capture: :all_but_first)
|> Enum.map(fn [a, b] -> String.to_integer(a) * String.to_integer(b) end)
|> Enum.sum()
end
def part2(input) do
Regex.scan(~r/mul\((\d+),(\d+)\)|(do\(\))|(don't\(\))/, input, capture: :all_but_first)
|> Enum.reduce({0, :process}, fn
["", "", "do()"], {sum, _} -> {sum, :process}
["", "", "", "don't()"], {sum, _} -> {sum, :skip}
_, {sum, :skip} -> {sum, :skip}
[a, b], {sum, :process} -> {sum + String.to_integer(a) * String.to_integer(b), :process}
end)
|> elem(0)
end
Empty placeholder for the preceding “do()” in the regex |-s. If you swap them then you will see the reverse scenario
Because (do\(\))
is the 3rd capture group, and (don't\(\))
is the 4th in your regex.
I wanted to do binary parsing as well but the regexes are too convenient here, so quick solution like a lot in this thread:
defmodule AdventOfCode.Solutions.Y24.Day03 do
alias AoC.Input
def parse(input, _part) do
String.trim(Input.read!(input))
end
def part_one(problem) do
~r/mul\(([0-9]{1,3}),([0-9]{1,3})\)/
|> Regex.scan(problem)
|> Enum.reduce(0, fn [_, a, b], acc ->
String.to_integer(a) * String.to_integer(b) + acc
end)
end
def part_two(problem) do
~r/(mul\(([0-9]{1,3}),([0-9]{1,3})\)|do\(\)|don't\(\))/
|> Regex.scan(problem)
|> Enum.reduce({0, true}, fn
["do()" | _], {acc, _enabled?} -> {acc, true}
["don't()" | _], {acc, _enabled?} -> {acc, false}
[_, _, a, b], {acc, true} -> {String.to_integer(a) * String.to_integer(b) + acc, true}
[_, _, _, _], {acc, false} -> {acc, false}
end)
|> elem(0)
end
end
@code-shoily @Aetherus makes sense, thanks! But how to prevent it while still using capture: :all_but_first
?
Maybe it’s not something that’s worth worrying about
My todays solution was a much quicker than the other days, and I am posting this in a hurry, trying to get into the next meeting in time…
Though I have to speak a work of warning:
Part 2 threw me for a loop because the inputs were all separated by lines. But overall pretty fun problem
defmodule AOC.Y2024.Day3 do
@moduledoc false
use AOC.Solution
@mul_regex ~r/mul\((\d+),(\d+)\)/
@do_regex ~r/do\(\)/
@dont_regex ~r/don\'t\(\)/
@impl true
def load_data() do
Data.load_day(2024, 3)
|> Enum.join()
end
@impl true
def part_one(data) do
Regex.scan(@mul_regex, data, capture: :all_but_first)
|> Enum.map(fn v -> Enum.map(v, &String.to_integer(&1)) end)
|> General.map_sum(fn [a, b] -> a * b end)
end
@impl true
def part_two(data) do
data
|> get_do_instructions()
|> Enum.concat(get_dont_instructions(data))
|> Enum.concat(get_mul_instructions(data))
|> Enum.sort(fn a, b -> elem(a, 0) < elem(b, 0) end)
|> Enum.reduce({0, :do}, fn
{_, :do}, {acc, _} -> {acc, :do}
{_, :dont}, {acc, _} -> {acc, :dont}
{_, :mul, a, b}, {acc, :do} -> {acc + a * b, :do}
{_, :mul, _, _}, {acc, :dont} -> {acc, :dont}
end)
|> elem(0)
end
defp get_mul_instructions(data),
do:
Regex.scan(@mul_regex, data, return: :index)
|> Enum.map(fn [{mul_idx, _}, {f, fl}, {s, sl}] ->
{mul_idx, :mul, data |> String.slice(f, fl) |> String.to_integer(),
data |> String.slice(s, sl) |> String.to_integer()}
end)
defp get_do_instructions(data),
do:
Regex.scan(@do_regex, data, capture: :first, return: :index)
|> Enum.flat_map(& &1)
|> Enum.map(fn {i, _} -> {i, :do} end)
defp get_dont_instructions(data),
do:
Regex.scan(@dont_regex, data, capture: :first, return: :index)
|> Enum.flat_map(& &1)
|> Enum.map(fn {i, _} -> {i, :dont} end)
end
Nice!
This made me realize that there is no case in my input where there would be a 4 digit number in the mul
expressions.
Regex
and no sub-binaries defp parse_instructions("mul(" <> rest, acc) do
parse_int_1(rest, 0, acc)
end
defp parse_instructions("do()" <> rest, acc) do
parse_instructions(rest, [:do | acc])
end
defp parse_instructions("don't()" <> rest, acc) do
parse_instructions(rest, [:dont | acc])
end
defp parse_instructions(<<_, rest::bytes>>, acc) do
parse_instructions(rest, acc)
end
defp parse_instructions(<<>>, acc) do
:lists.reverse(acc)
end
defp parse_int_1(<<c, rest::bytes>>, inner_acc, outer_acc) when c in ?0..?9 do
parse_int_1(rest, inner_acc * 10 + c - ?0, outer_acc)
end
defp parse_int_1(<<?,, rest::bytes>>, inner_acc, outer_acc) do
parse_int_2(rest, 0, [inner_acc | outer_acc])
end
defp parse_int_1(<<_, rest::bytes>>, _inner_acc, outer_acc) do
parse_instructions(rest, outer_acc)
end
defp parse_int_2(<<c, rest::bytes>>, inner_acc, outer_acc) when c in ?0..?9 do
parse_int_2(rest, inner_acc * 10 + c - ?0, outer_acc)
end
defp parse_int_2(<<?), rest::bytes>>, r, [l | outer_acc]) do
parse_instructions(rest, [{:mul, l, r} | outer_acc])
end
defp parse_int_2(<<_, rest::bytes>>, _inner_acc, [_l | outer_acc]) do
parse_instructions(rest, outer_acc)
end
Full: aoc2024/lib/day3.ex at master · ruslandoga/aoc2024 · GitHub