Skip to content

Commit

Permalink
Year 2023: Day 03
Browse files Browse the repository at this point in the history
  • Loading branch information
joshleaves committed Feb 2, 2024
1 parent 5450e20 commit 6e2c3ca
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ AllCops:

Layout/DotPosition:
EnforcedStyle: trailing
Layout/ElseAlignment:
Enabled: false
Layout/EndAlignment:
Enabled: false
Layout/HashAlignment:
Enabled: false

Layout/MultilineMethodCallBraceLayout:
EnforcedStyle: new_line
Layout/MultilineMethodCallIndentation:
Expand Down
140 changes: 140 additions & 0 deletions spec/year_2023/day_03_input

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions spec/year_2023/day_03_sample_one
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
12 changes: 12 additions & 0 deletions spec/year_2023/day_03_sample_one-bis
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
12.......*..
+.........34
.......-12..
..78........
..*....60...
78..........
.......23...
....90*12...
............
2.2......12.
.*.........*
1.1.......56
3 changes: 3 additions & 0 deletions spec/year_2023/day_03_sample_one-five
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
........
.24..4..
......*.
8 changes: 8 additions & 0 deletions spec/year_2023/day_03_sample_one-quad
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
..504.*
*.102..
.......
.100...
.......
.*.....
.......
.200...
12 changes: 12 additions & 0 deletions spec/year_2023/day_03_sample_one-seven
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.......5......
..7*..*.....4*
...*13*......9
.......15.....
..............
..............
..............
..............
..............
..............
21............
...*9.........
3 changes: 3 additions & 0 deletions spec/year_2023/day_03_sample_one-six
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
....................
..-52..52-..52..52..
..................-.
12 changes: 12 additions & 0 deletions spec/year_2023/day_03_sample_one-ter
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
12.......*..
+.........34
.......-12..
..78........
..*....60...
78.........9
.5.....23..$
8...90*12...
............
2.2......12.
.*.........*
1.1..503+.56
10 changes: 10 additions & 0 deletions spec/year_2023/day_03_sample_two
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..
55 changes: 55 additions & 0 deletions spec/year_2023/day_03_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'year_2023/day_03'

describe Year2023::Day03 do
context 'Part 1' do
subject do
Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one'), true)
end

it 'cleans up non-connected number parts' do
expect(subject.lines[0].line).to eq('467.......')
expect(subject.lines[5].line).to eq('.....+....')
end

it 'gives a final result' do
expect(subject.to_i).to eq(4361)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-bis'), true).to_i).to eq(413)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-ter'), true).to_i).to eq(925)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-quad'), true).to_i).to eq(0)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-five'), true).to_i).to eq(4)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-six'), true).to_i).to eq(156)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-seven'), true).to_i).to eq(62)
end
end

context 'Part 2' do
subject do
Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_two'))
end

it 'cleans up non-connected number parts' do
expect(subject.lines[0].line).to eq('467.......')
expect(subject.lines[5].line).to eq('.....+....')
end

it 'gives a final result' do
expect(subject.to_i).to eq(467835)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-bis')).to_i).to eq(6756)
expect(Year2023::Day03.new(File.read('spec/year_2023/day_03_sample_one-ter')).to_i).to eq(6756)
end
end

context 'Results' do
subject do
File.read('spec/year_2023/day_03_input')
end

it 'correctly answers part 1' do
expect(Year2023::Day03.new(subject, true).to_i).to eq(540212)
end

it 'correctly answers part 2' do
# expect(Year2023::Day03.new(subject).to_i).to eq(87605697)
end
end
end
26 changes: 25 additions & 1 deletion year_2023.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,28 @@ Again, we are [Regexp](https://ruby-doc.org/core-2.5.1/Regexp.html) in both part

For the first one, we have to make sure none of values is higher than the limits, which is pretty straight-forward.

A class `LinePartTwo` has been made to differentiate behavior for part 2.
A class `LinePartTwo` has been made to differentiate behavior for part 2.


## Day 03

```
Year2023::Day03
Part 1
cleans up non-connected number parts
gives a final result
Part 2
cleans up non-connected number parts
gives a final result
Results
correctly answers part 1
correctly answers part 2
```

Oh boy, was that one complicated...

I'm clearly not good enough with spatial representations (something about off-by-one errors...), so I knew RIGHT AWAY that trying to map everything as `[Item(X, Y, Z, number), Item(X, Y, Z, symbol)]` would be too much of an endeavor for me, but this...was though. The decision to run tests as "scenarios following the samples" instead of using proper method-based unit testing made things even worse, and sticking to the same `Line` type of class as the previous days was completely stupid and shouldn't even have worked.

Maybe I'll revisit this one with a better solution.

Also, thanks to the kind people or [/r/adventofcode](https://www.reddit.com/r/adventofcode/comments/189q9wv/2023_day_3_another_sample_grid_to_use/) for the sample grids they provided, they were a great help in debugging the first part properly.
154 changes: 154 additions & 0 deletions year_2023/day_03.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
class Year2023
class Day03
attr_reader :input, :lines, :version

class Line
attr_reader :line

def initialize(input_line)
@line = input_line
end

def find_group_length(idx)
return 0 unless @line[idx]
return 0 unless @line[idx] =~ /\d/

1 + find_group_length(idx + 1)
end

def range_got_symbol?(idx_from, idx_to)
range = @line[idx_from, idx_to]
range = range.gsub(/\d|\./, '')
range.length.positive?
end

def erase_range(idx, number_part_length)
0.upto(number_part_length - 1) do |i|
@line[idx + i] = '.'
end
end

def number_part_is_island?(idx, number_part_length, above, below)
diagonal_left = idx.zero? ? idx : idx - 1
diagonal_right = idx.zero? ? number_part_length + 1 : number_part_length + 2

range_above = above&.range_got_symbol?(diagonal_left, diagonal_right)
range_below = below&.range_got_symbol?(diagonal_left, diagonal_right)
# puts "-> ABOVE: #{above&.line[diagonal_left, diagonal_right]} => #{range_above}" if above
# puts "-> BELOW: #{below&.line[diagonal_left, diagonal_right]} => #{range_below}" if below
range_left = idx.zero? ? false : range_got_symbol?(idx - 1, 1)
range_right = range_got_symbol?(idx + number_part_length, 1)
# puts "-> LEFT: #{idx.zero? ? false : line[idx - 1, 1]} => #{range_left}"
# puts "-> RIGHT: #{line[idx + number_part_length, 1]} => #{range_right}"

[range_above, range_below, range_left, range_right].any?(true)
end

def cleanup(previous_line, next_line)
idx = 0
# puts ""
# puts "__#{line}__"
while @line[idx]
next idx += 1 unless @line[idx] =~ /\d/

number_part_length = find_group_length(idx)
# puts "NUM: _#{line[idx, number_part_length]}_"
got_match = number_part_is_island?(idx, number_part_length, previous_line, next_line)
erase_range(idx, number_part_length) unless got_match

idx += number_part_length
end
end

def to_i
@line.scan(/\d+/).map(&:to_i).inject(&:+) || 0
end
end

class LinePartTwo < Line
attr_reader :numbers

def initialize(input_line, line_idx)
super(input_line)
@numbers = []
@line_idx = line_idx
end

def range_got_symbol?(idx_from, idx_to)
return false if idx_from.negative?

range = @line[idx_from, idx_to]
range.index('*')
end

def find_gears_vertical(above, below, diagonal_left, diagonal_right)
[
above&.range_got_symbol?(diagonal_left, diagonal_right),
below&.range_got_symbol?(diagonal_left, diagonal_right)
]
end

def find_gears_horizontal(idx, number_part_length)
[
range_got_symbol?(idx - 1, 1),
range_got_symbol?(idx + number_part_length, 1)
]
end

def calculate_diagonals(idx, number_part_length)
[
idx.zero? ? idx : idx - 1,
idx.zero? ? number_part_length + 1 : number_part_length + 2
]
end

def number_part_is_island?(idx, number_part_length, above, below)
diagonal_left, diagonal_right = calculate_diagonals(idx, number_part_length)

gear_above, gear_below = find_gears_vertical(above, below, diagonal_left, diagonal_right)
gear_left, gear_right = find_gears_horizontal(idx, number_part_length)
return false unless [gear_above, gear_below, gear_left, gear_right].any?{|x| x.is_a?(Numeric) }

add_gears_vertical(line[idx, number_part_length].to_i, diagonal_left, gear_above, gear_below)
add_gears_horizontal(line[idx, number_part_length].to_i, idx, number_part_length, gear_left, gear_right)
true
end

def add_gears_vertical(number, diagonal_left, gear_above, gear_below)
numbers << [number, [@line_idx - 1, diagonal_left + gear_above]] if gear_above
numbers << [number, [@line_idx + 1, diagonal_left + gear_below]] if gear_below
end

def add_gears_horizontal(number, idx, number_part_length, gear_left, gear_right)
numbers << [number, [@line_idx, idx - 1]] if gear_left
numbers << [number, [@line_idx, idx + number_part_length]] if gear_right
end
end

def initialize(input_file, part_one = false)
@version = part_one ? 1 : 2
@input = input_file
@lines = input_file.chomp.split("\n").map.each_with_index do |input_line, line_idx|
(@version == 1 ? Line.new(input_line) : LinePartTwo.new(input_line, line_idx))
end
@lines.each_with_index do |line, i|
line.cleanup(i.zero? ? nil : @lines[i - 1], @lines[i + 1])
end
end

def to_i
return to_i_v2 if @version == 2

@lines.map(&:to_i).compact.inject(&:+)
end

def to_i_v2
all_lines = @lines.map(&:numbers).inject(&:+).group_by(&:last)
all_lines.map do |_position, values|
next 0 unless values.length == 2

values[0].first * values[1].first
end.inject(&:+)
end
end
end

0 comments on commit 6e2c3ca

Please sign in to comment.