Advent of Code 2024 - Day 3

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.

4 Likes

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 
1 Like

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 :laughing: )

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() 
2 Likes

I haven’t used regex in a while - made a few dumb mistakes before getting it right, lol.

1 Like

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) 
3 Likes

Hey!

This is my solution for Day 03.

Part 1

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() 

Part 2

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) 
5 Likes

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.

1 Like

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.

4 Likes

This is the way.

3 Likes

Just some average-ugly regexes.

3 Likes

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

1 Like

Because (do\(\)) is the 3rd capture group, and (don't\(\)) is the 4th in your regex.

2 Likes

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 
2 Likes

@code-shoily @Aetherus makes sense, thanks! But how to prevent it while still using capture: :all_but_first? :smirk:

Maybe it’s not something that’s worth worrying about :man_shrugging:

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:

:warning: This solution is RegEx free :warning:

3 Likes

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 
1 Like

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.

No 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

2 Likes