Skip to content

Commit

Permalink
Advent of Code 2024 - Day 3
Browse files Browse the repository at this point in the history
  • Loading branch information
cdalvaro committed Dec 23, 2024
1 parent bd761fc commit 68ffec3
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
pull_request:

env:
SKIP_SLOW_TESTS: true
RUN_SLOW_TESTS: 0

jobs:
test:
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,35 @@
This repo contains my solutions to the [Advent of Code](https://adventofcode.com) challenges, written in
the [Ruby](https://www.ruby-lang.org) programming language.

The project is compose of two main parts:
The project is structured as follows:

- A library, located in the [`lib/`](lib/) directory, containing the code for the solutions.
- A set of tests, located in the [`test/`](test/) directory, containing the tests for the solutions.
- A script [`bin/create-files.rb`](bin/create-files.rb) to create the basic files for a new puzzle.
- A library, located in the [`lib/`](lib) directory, containing the code for the solutions.
- A set of tests, located in the [`test/`](test) directory, containing the tests for the solutions.
- The [`sig`](sig) directory, which contains the signatures of the project.

## Create Files

When starting a new puzzle, you can use the script [`bin/create-files.rb`](bin/create-files.rb) to create the basic files for the puzzle.

```bash
bin/create-files.rb --day 25 [--year=2024] [--force]
```

## Run Tests

Tests are written using the [minitest](https://github.com/minitest/minitest) framework.

To run the tests, simply run the following command:

```bash
bundle install
SKIP_SLOW_TESTS=1 bundle exec rake test
bundle exec rake test
```

## Puzzles
Some tests are marked as slow, and they are skipped by default. To run them, you can set the environment variable `RUN_SLOW_TESTS` to `1`.

## Solved Puzzles

<a href="https://www.ruby-lang.org"><img src="https://s3.cdalvaro.io/github.com/cdalvaro/advent-of-code/Holly.png" width="230px" align="right"/></a>

Expand All @@ -39,7 +53,12 @@ SKIP_SLOW_TESTS=1 bundle exec rake test
</tr>
<tr>
<td>2️⃣ <a href="https://adventofcode.com/2024/day/2">Red-Nosed Reports</a></td>
<td><a href="lib/puzzles/2024/day02"><code>lib/puzzles/2024/day01</code></a></td>
<td><a href="lib/puzzles/2024/day02"><code>lib/puzzles/2024/day02</code></a></td>
<td>🌟🌟</td>
</tr>
<tr>
<td>3️⃣ <a href="https://adventofcode.com/2024/day/3">Mull It Over</a></td>
<td><a href="lib/puzzles/2024/day03"><code>lib/puzzles/2024/day03</code></a></td>
<td>🌟🌟</td>
</tr>
</table>
Expand Down
52 changes: 52 additions & 0 deletions lib/puzzles/2024/day03/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# [Day 3: Mull It Over](https://adventofcode.com/2024/day/3)

## Part One

"Our computers are having issues, so I have no idea if we have any Chief Historians in stock! You're welcome to check the warehouse, though," says the mildly flustered shopkeeper at the [North Pole Toboggan Rental Shop](https://adventofcode.com/2020/day/2). The Historians head out to take a look.

The shopkeeper turns to you. "Any chance you can see why our computers are having issues again?"

The computer appears to be trying to run a program, but its memory (your puzzle input) is **corrupted**. All the instructions have been jumbled up!

It seems like the goal of the program is just to **multiply some numbers**. It does that with instructions like `mul(X,Y)`, where `X` and `Y` are each 1-3 digit numbers. For instance, `mul(44,46)` multiplies `44` by `46` to get a result of `2024`. Similarly, `mul(123,4)` would multiply `123` by `4`.

However, because the program's memory has been corrupted, there are also many invalid characters that should be ignored, even if they look like part of a `mul` instruction. Sequences like `mul(4*`, `mul(6,9!`, `?(12,34)`, or `mul ( 2 , 4 )` do **nothing**.

For example, consider the following section of corrupted memory:

`x` `mul(2,4)` `%&mul[3,7]!@^do_not_` `mul(5,5)` `+mul(32,64]then(` `mul(11,8)mul(8,5)` `)`

Only the four highlighted sections are real `mul` instructions. Adding up the result of each instruction produces **`161`** `(2*4 + 5*5 + 11*8 + 8*5)`.

Scan the corrupted memory for uncorrupted mul instructions. **What do you get if you add up all the results of the multiplications?**

To play, please identify yourself via one of these services:

Your puzzle answer was `173,529,487`.

**The first half of this puzzle is complete! It provides one gold star:** 🌟

## Part Two

As you scan through the corrupted memory, you notice that some of the conditional statements are also still intact. If you handle some of the uncorrupted conditional statements in the program, you might be able to get an even more accurate result.

There are two new instructions you'll need to handle:

- The `do()` instruction **enables** future `mul` instructions.
- The `don't()` instruction disables future `mul` instructions.

Only the **most recent** `do()` or `don't()` instruction applies. At the beginning of the program, `mul` instructions are **enabled**.

For example:

`x` `mul(2,4)` `&mul[3,7]!^` `don't()` `_mul(5,5)+mul(32,64](mul(11,8)un` `do()` `?` `mul(8,5)` `)`

This corrupted memory is similar to the example from before, but this time the `mul(5,5)` and `mul(11,8)` instructions are **disabled** because there is a `don't()` instruction before them. The other `mul` instructions function normally, including the one at the end that gets re-**enabled** by a `do()` instruction.

This time, the sum of the results is **`48`** `(2*4 + 8*5)`.

Handle the new instructions; **what do you get if you add up all the results of just the enabled multiplications?**

Your puzzle answer was `99,532,691`.

**Both parts of this puzzle are complete! They provide two gold stars:** 🌟🌟
13 changes: 13 additions & 0 deletions lib/puzzles/2024/day03/day03.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require_relative "part1"
require_relative "part2"

module AdventOfCode
module Puzzles2024
##
# {include:file:lib/puzzles/2024/day03/README.md}
module Day03
end
end
end
6 changes: 6 additions & 0 deletions lib/puzzles/2024/day03/input.txt

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions lib/puzzles/2024/day03/part1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module AdventOfCode
module Puzzles2024
module Day03
##
# Class for solving Day 3 (2024) - Part 1 puzzle
class Part1
##
# The program memory
# @return [String] the memory
attr_reader :memory

##
# @param file [String|nil] file with puzzle input
def initialize(file: nil)
file ||= "#{File.dirname(__FILE__)}/input.txt"
init_contents File.readlines(file, chomp: true)
end

##
# Compute the answer for the puzzle.
# @return [Integer] answer for the puzzle
def answer
ops = find_valid_ops
compute_ops(ops)
end

protected

##
# Initialize the class' contents from the file contents.
def init_contents(file_contents)
@memory = file_contents.join
end

##
# Find all valid operations in the memory
# @return [Array<String>] the valid operations
def find_valid_ops
matches = memory.scan(%r{mul\(\d{1,3},\d{1,3}\)})
matches.map(&:to_s)
end

##
# Sum the results of all operations
# @param ops [Array<String>] the operations to sum
# @return [Integer] the sum of all operations
def compute_ops(ops)
result = 0
ops.each do |op|
result += ::Regexp.last_match(1).to_i * ::Regexp.last_match(2).to_i if op =~ %r{(\d+),(\d+)}
end

result
end
end
end
end
end
44 changes: 44 additions & 0 deletions lib/puzzles/2024/day03/part2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

require_relative "part1"

module AdventOfCode
module Puzzles2024
module Day03
##
# Class for solving Day 3 (2024) - Part 2 puzzle
class Part2 < Part1
protected

##
# Find all valid operations in the memory
# @return [Array<String>] the valid operations
def find_valid_ops
matches = memory.scan(%r{mul\(\d{1,3},\d{1,3}\)|do(?:n't)?\(\)})
filter_ops matches.map(&:to_s)
end

##
# Filter valid operations regarding the enabled flag,
# which is set by the do() and don't() operations.
# @param ops [Array<String>] the operations to filter
def filter_ops(ops)
valid_ops = []
enabled = true
ops.each do |op|
if op == "do()"
enabled = true
next
elsif op == "don't()"
enabled = false
next
end

valid_ops << op if enabled
end
valid_ops
end
end
end
end
end
20 changes: 20 additions & 0 deletions sig/puzzles/2024/day03.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module AdventOfCode
module Puzzles2024
module Day03
class Part1
attr_reader memory: String

def initialize: (file: String?) -> void

def answer: () -> Integer
def init_contents: (Array[String]) -> void
def find_valid_ops: () -> Array[String]
def compute_ops: (Array[String]) -> Integer
end

class Part2 < Part1
def filter_ops: (Array[String]) -> Array[String]
end
end
end
end
17 changes: 17 additions & 0 deletions sig/test/puzzles/2024/day03_test.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module AdventOfCode
module Test
module Puzzles2024
module Day03
class Part1Test < MiniTest::Test
def test_part1_answer_real_set: -> untyped
def test_part1_answer_test_set: -> untyped
end

class Part2Test < MiniTest::Test
def test_part2_answer_real_set: -> untyped
def test_part2_answer_test_set: -> untyped
end
end
end
end
end
2 changes: 1 addition & 1 deletion test/puzzles/2023/day05/day05_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_answer_test_data_set
end

def test_answer_input_set
skip "Takes too long to run" if ENV["SKIP_SLOW_TESTS"]
skip "Takes too long to run" unless ENV.fetch("RUN_SLOW_TESTS", 0).to_i == 1

puzzle = AdventOfCode::Puzzles2023::Day05::Part2.new

Expand Down
2 changes: 1 addition & 1 deletion test/puzzles/2023/day10/day10_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_answer_test_data_set5
end

def test_answer_input_set
skip "Takes too long to run" if ENV["SKIP_SLOW_TESTS"]
skip "Takes too long to run" unless ENV.fetch("RUN_SLOW_TESTS", 0).to_i == 1

puzzle = AdventOfCode::Puzzles2023::Day10::Part2.new

Expand Down
62 changes: 62 additions & 0 deletions test/puzzles/2024/day03/day03_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

require "test_helper"
require "puzzles/2024/day03/day03"

module AdventOfCode
module Test
module Puzzles2024
module Day03
##
# Tests Day 3 (2024) - Part 1
class Part1Test < Minitest::Test
def setup
# Do nothing
end

def teardown
# Do nothing
end

def test_part1_answer_test_set
input_file = "#{File.dirname(__FILE__)}/test_data_part1.txt"
puzzle = AdventOfCode::Puzzles2024::Day03::Part1.new(file: input_file)

assert_equal 161, puzzle.answer
end

def test_part1_answer_real_set
puzzle = AdventOfCode::Puzzles2024::Day03::Part1.new

assert_equal 173_529_487, puzzle.answer
end
end

##
# Tests Day 3 (2024) - Part 2
class Part2Test < Minitest::Test
def setup
# Do nothing
end

def teardown
# Do nothing
end

def test_part2_answer_test_set
input_file = "#{File.dirname(__FILE__)}/test_data_part2.txt"
puzzle = AdventOfCode::Puzzles2024::Day03::Part2.new(file: input_file)

assert_equal 48, puzzle.answer
end

def test_part2_answer_real_set
puzzle = AdventOfCode::Puzzles2024::Day03::Part2.new

assert_equal 99_532_691, puzzle.answer
end
end
end
end
end
end
1 change: 1 addition & 0 deletions test/puzzles/2024/day03/test_data_part1.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))
1 change: 1 addition & 0 deletions test/puzzles/2024/day03/test_data_part2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))

0 comments on commit 68ffec3

Please sign in to comment.