Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advent of Code 2023 - Day 2 #2

Merged
merged 3 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<a href="https://www.ruby-lang.org"><img src="https://s3.cdalvaro.io/github.com/cdalvaro/advent-of-code-2023/RubyLang.png" alt="Ruby" width="64px" margin="5px" align="right"/></a>

# Advent of Code 2023

This repo contains my solutions to the [Advent of Code 2023](https://adventofcode.com/2023) challenges, written in the [Ruby](https://www.ruby-lang.org) programming language.
Expand All @@ -18,10 +20,10 @@ bundle exec rake test

## Puzzles

<a href="https://www.ruby-lang.org"><img src="https://s3.cdalvaro.io/github.com/cdalvaro/advent-of-code-2023/RubyLang.png" alt="Ruby" width="86px" margin="5px" align="right"/></a>

| Day | Solution | Rank |
|--------------------:|:-----------------------------------------------:|:-----:|
| [**1**][day01_link] | [`lib/puzzles/day01.rb`](lib/puzzles/day01.rb) | ⭐🌟✴️ |
| Day | Solution | Rank |
|------------------:|:----------------------------------------------:|:-----:|
| [1️⃣][day01_link] | [`lib/puzzles/day01.rb`](lib/puzzles/day01.rb) | ⭐🌟✴️ |
| [2️⃣][day02_link] | [`lib/puzzles/day02.rb`](lib/puzzles/day02.rb) | ⭐🌟✴️ |

[day01_link]: https://adventofcode.com/2023/day/1
[day02_link]: https://adventofcode.com/2023/day/2
15 changes: 7 additions & 8 deletions lib/puzzles/day01.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ module Puzzles
module Day01
##
# Class for solving Day 01 - Part 1 puzzle
#
# On each line, the calibration value can be found by combining
# the first digit and the last digit (in that order) to form a
# single two-digit number.
class Part1
attr_reader :file_contents

##
# @param file [String] file with puzzle input
def initialize(file:)
@file_contents = File.readlines(file, chomp: true)
end

##
# Compute the answer for the puzzle.
# The answer is the sum of all calibration values.
#
# @return [Integer] answer for the puzzle
def answer
calibration_values.sum
end
Expand Down Expand Up @@ -68,10 +71,6 @@ def find_last_number(line)

##
# Class for solving Day 01 - Part 2 puzzle
#
# It looks like some of the digits are actually spelled out with
# letters: one, two, three, four, five, six, seven, eight, and
# nine also count as valid "digits".
class Part2 < Part1
protected

Expand Down
192 changes: 192 additions & 0 deletions lib/puzzles/day02.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# frozen_string_literal: true

module AdventOfCode
module Puzzles
##
# Advent of Code 2023 - Day 2
# https://adventofcode.com/2023/day/2
module Day02
##
# Set class to store the number of blue, green, and red cubes.
class Set
attr_reader :blue, :green, :red

##
# @param blue [Integer] number of blue cubes
# @param green [Integer] number of green cubes
# @param red [Integer] number of red cubes
def initialize(blue: 0, green: 0, red: 0)
@blue = blue
@green = green
@red = red
end

##
# Compute the power of the set by multiplying the number of
# blue, green, and red cubes.
#
# @return [Integer] power of the set
def power
return @power if @power

@power = (blue || 1) * (green || 1) * (red || 1)
end
end

##
# Game class to store the game id and its sets.
class Game
attr_reader :id, :sets

##
# @param id [Integer] game id
# @param sets [Array<Set>] sets of cubes
def initialize(id:, sets:)
@id = id
@sets = sets
end

##
# Check if the game is valid given the number of blue, green,
# and red cubes.
#
# @param blue [Integer] number of blue cubes
# @param green [Integer] number of green cubes
# @param red [Integer] number of red cubes
#
# @return [Boolean] true if the game is valid, false otherwise
def valid?(blue:, green:, red:)
# A game is valid only if every set has the same or less
# number of blue, green, and red cubes as the set it is
sets.each do |set|
return false if set.blue > blue
return false if set.green > green
return false if set.red > red
end
true
end
end

##
# Class for solving Day 02 - Part 1 puzzle
class Part1
attr_reader :games

##
# @param file [String] path to the input file
def initialize(file:)
file_contents = File.readlines(file, chomp: true)
load_games(file_contents)
end

##
# Find games that are valid given the number of blue, green,
# and red cubes.
#
# @param blue [Integer] number of blue cubes
# @param green [Integer] number of green cubes
# @param red [Integer] number of red cubes
#
# @return [Array<Game>] valid games
def valid_games(blue:, green:, red:)
games.select { |game| game.valid?(blue:, green:, red:) }
end

##
# Find the sum of the game ids that are valid given the number
# of blue, green, and red cubes.
#
# @param blue [Integer] number of blue cubes
# @param green [Integer] number of green cubes
# @param red [Integer] number of red cubes
#
# @return [Integer] sum of the game ids
def answer(blue:, green:, red:)
valid_games(blue:, green:, red:).map(&:id).sum
end

protected

##
# Load games from the file contents.
#
# @param file_contents [Array<String>] file contents
#
# @return [Array<Game>] games
def load_games(file_contents)
@games = file_contents.map do |line|
id, sets = line.split(": ")
sets = sets.split(%r{;\s+}).map { |set| parse_set(set) }
game_id = parse_game_id(id)
Game.new(id: game_id, sets:)
end
end

##
# Parse the game id from a string.
# The string is in the format "Game X".
#
# @param game [String] game id
#
# @return [Integer] game id
def parse_game_id(game)
game.gsub("Game ", "").to_i
end

##
# Parse a set of cubes from a string.
# The string is in the format "X blue, Y green, Z red".
#
# @param set [String] set of cubes
#
# @return [Set] set of cubes
def parse_set(set)
colors = set.split(%r{,\s+}).map(&:split)
colors = colors.to_h { |number, color| [color.downcase.to_sym, number.to_i] }
Set.new(**colors)
end
end

##
# Class for solving Day 02 - Part 2 puzzle
class Part2 < Part1
##
# Find the sum of the powers for the minimum valid sets.
#
# @return [Integer] sum of the powers
def answer
minimum_valid_sets.values.map(&:power).sum
end

##
# Find the minimum valid set for each game.
#
# @return [Hash<Game, Set>] minimum valid sets
def minimum_valid_sets
@minimum_valid_sets ||= games.to_h { |game| [game, minimum_valid_set_for(game:)] }
end

protected

##
# Find the minimum valid set for a game.
# The minimum valid set is the set with the maximum number of
# blue, green, and red cubes for all sets.
#
# @param game [Game] game to find the minimum valid set for
#
# @return [Set] minimum valid set
def minimum_valid_set_for(game:)
# Find the maximum number of blue, green, and red cubes in each set
minimum_set = { blue: 0, green: 0, red: 0 }
game.sets.each do |set|
%i[blue green red].each do |color|
minimum_set[color] = [set.send(color), minimum_set[color]].max
end
end
Set.new(**minimum_set)
end
end
end
end
end
38 changes: 18 additions & 20 deletions sig/puzzles/day01.rbs
Original file line number Diff line number Diff line change
@@ -1,37 +1,35 @@
module AdventOfCode
module Puzzles
module Day01
module Puzzles
module Day01

class Part1
class Part1

@calibration_values: [Integer]
@calibration_values: [Integer]

@file_contents: [String]
attr_reader file_contents: [String]

def answer: () -> Integer
def answer: () -> Integer

def calibration_values: () -> [Integer]
def calibration_values: () -> [Integer]

def file_contents: () -> [String]
def find_first_number: () -> Integer?

def find_first_number: () -> Integer?
def find_last_number: () -> Integer?

def find_last_number: () -> Integer?
def select_number: (String) -> Integer
end

def select_number: (String) -> Integer
end

class Part2
class Part2

TRANSLATIONS: { String: Integer }
TRANSLATIONS: { String: Integer }

def find_first_number: (String) -> Integer
def find_first_number: (String) -> Integer

def find_last_number: (String) -> Integer
def find_last_number: (String) -> Integer

def translate_contents: () -> void
def translate_contents: () -> void

end
end
end
end
end
end
52 changes: 52 additions & 0 deletions sig/puzzles/day02.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module AdventOfCode
module Puzzles
module Day02

class Set

@power: Integer

attr_reader blue: Integer
attr_reader green: Integer
attr_reader red: Integer

def power: () -> Integer
end

class Game

attr_reader id: Integer
attr_reader sets: [Set]

def valid?: (Integer, Integer, Integer) -> bool
end

class Part1

attr_reader games: [Game]

def answer: (Integer, Integer, Integer) -> Integer

def load_games: ([String]) -> [Game]

def parse_game_id: (String) -> Integer

def parse_set: (String) -> Set

def valid_games: (Integer, Integer, Integer) -> [Game]
end

class Part2

@minimum_valid_sets: {Game: Set}

def answer: () -> Integer

def minimum_valid_sets: () -> {Game: Set}

def minimum_valid_set_for: (Game) -> Set
end

end
end
end
27 changes: 27 additions & 0 deletions sig/test/puzzles/day02_test.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Puzzles
module Day02
class Part1Test
def setup: -> untyped

def teardown: -> untyped

def test_answer_input_set: -> untyped

def test_answer_test_data_set: -> untyped

def test_valid_games_test_data_set: -> untyped
end

class Part2Test
def setup: -> untyped

def teardown: -> untyped

def test_answer_input_set: -> untyped

def test_answer_test_data_set: -> untyped

def test_valid_games_test_data_set: -> untyped
end
end
end
Loading