-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpart1.rb
185 lines (155 loc) Β· 5.69 KB
/
part1.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# frozen_string_literal: true
require_relative "pair"
module AdventOfCode
module Puzzles2024
module Day04
##
# Class for solving Day 4 (2024) - Part 1 puzzle
class Part1
##
# The text where searching for the answer
attr_reader :text
##
# The height of the text
attr_reader :height
##
# The width of the text
attr_reader :width
##
# @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.
# <Add description here>
#
# @return [Integer] answer for the puzzle
def answer
x_indexes = find_indexes_for(pattern: "X")
x_indexes.sum { |pair| number_of_xmas_words_for_pair(pair) }
end
protected
##
# Initialize the class' contents from the file contents.
def init_contents(file_contents)
@text = file_contents
@height = text.length
@width = text[0]&.length || 0
end
##
# Find all the indexes for the given pattern in the text
# @return [Array<Pair>] The indexes of the pattern in the text
def find_indexes_for(pattern:)
indexes = []
text.each_with_index do |line, row_idx|
line.enum_for(:scan, pattern).map { Regexp.last_match&.begin(0) }.each do |col_idx|
indexes << Pair.new(row_idx, col_idx)
end
end
indexes
end
##
# Return the number of XMAS words for the given pair
# @param pair [Pair] The position of the X
# @return [Integer] The number of XMAS words
def number_of_xmas_words_for_pair(pair)
possible_movements(pair).inject(0) do |acc, m|
acc + (compose_word(m.first, m.second) == "XMAS" ? 1 : 0)
end
end
##
# Return the possible movements for the given pair
# @param pair [Pair] The position of the X
# @return [Array<Pair>] The possible movements
def possible_movements(pair)
movements = []
movements += horizontal_movements(pair)
movements += vertical_movements(pair)
movements += diagonal1_movements(pair)
movements += diagonal2_movements(pair)
movements.select { |m| check_bounds(m.first, m.second) }
end
##
# Check if the given rows and cols are within the bounds of the text
# @param rows [Array<Integer>] The rows
# @param cols [Array<Integer>] The cols
# @return [Boolean] True if the rows and cols are within the bounds
def check_bounds(rows, cols)
rows.min >= 0 && rows.max < height && cols.min >= 0 && cols.max < width
end
##
# Compose the word from the given rows and cols
# @param rows [Array<Integer>] The rows
# @param cols [Array<Integer>] The cols
# @return [String] The composed word
def compose_word(rows, cols)
rows.zip(cols).map { |r, c| text[r][c] }.join
end
##
# Return the possible horizontal movements for the given pair
# @param pair [Pair] The position of the X
# @return [Array<Pair>] The possible movements
def horizontal_movements(pair)
rows = Array.new(4, pair.first)
movements = []
## Left to right
cols = (pair.second..pair.second + 3).to_a
movements << Pair.new(rows, cols)
## Right to left
cols = (pair.second - 3..pair.second).to_a.reverse
movements << Pair.new(rows, cols)
movements
end
##
# Return the possible vertical movements for the given pair
# @param pair [Pair] The position of the X
# @return [Array<Pair] The possible movements
def vertical_movements(pair)
cols = Array.new(4, pair.second)
movements = []
## Top to bottom
rows = (pair.first..pair.first + 3).to_a
movements << Pair.new(rows, cols)
## Bottom to top
rows = (pair.first - 3..pair.first).to_a.reverse
movements << Pair.new(rows, cols)
movements
end
##
# Return the possible diagonal (1) movements for the given pair
# @param pair [Pair] The position of the X
# @return [Array<Pair>] The possible movements
def diagonal1_movements(pair)
movements = []
## Top left to bottom right
rows = (pair.first..pair.first + 3).to_a
cols = (pair.second..pair.second + 3).to_a
movements << Pair.new(rows, cols)
## Bottom right to top left
rows = (pair.first - 3..pair.first).to_a.reverse
cols = (pair.second - 3..pair.second).to_a.reverse
movements << Pair.new(rows, cols)
movements
end
##
# Return the possible diagonal (2) movements for the given pair
# @param pair [Pair] The position of the X
# @return [Array<Pair>] The possible movements
def diagonal2_movements(pair)
movements = []
## Top right to bottom left
rows = (pair.first..pair.first + 3).to_a
cols = (pair.second - 3..pair.second).to_a.reverse
movements << Pair.new(rows, cols)
## Bottom left to top right
rows = (pair.first - 3..pair.first).to_a.reverse
cols = (pair.second..pair.second + 3).to_a
movements << Pair.new(rows, cols)
movements
end
end
end
end
end