aoc_rb_helpers 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9049a2637e249afd1d2670f1b4ca3616aa21ef94ae10efb271178693ff16f63
4
- data.tar.gz: 485a04d7594ef25fbec3fb2ca19060c8f5699a30fb0845ec9c985a899aae5be4
3
+ metadata.gz: 1990cde2622df33946ce6131918c215ac548d3b013ed36b57fd980fd0ba7c0ab
4
+ data.tar.gz: 7070a8add7b5f3bf0b4fa033959ce30624edeac87329a95964e2bade9dbb5741
5
5
  SHA512:
6
- metadata.gz: 6343e8180d5ab30baecad403c7765b4a666562a713154541c70ee6a602ecaa3253c08a25f89db76d36e4e86371901564b34eb7ba779fbbaa2e011cb543edf10a
7
- data.tar.gz: dade32999ee76bbda52f4615cad719ab2dc4ab2209dccf88b6756b0f63d125d7dfe40b76d61e85245c599b472b29e426c5dd110b91c5c3eb46bd8ffd1ab96f84
6
+ metadata.gz: 402844433642a9dcfd4bcded006ec9eca2d3cc8b18a0016b7d3cc45631b24e54e37315af3cff1d895c511233a167b9cc82ed21f7f942f721cafe016f8ea552d6
7
+ data.tar.gz: 34f664b1de9a6fd751241afc34445c46cc16ab6cce24f246476799d7915ce4f519f4ac67d22a42b72780c673d5c21fe693c19c42786a23f6b529c19beffa53f8
data/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
  ## [Unreleased]
8
8
  - No unreleased changes!
9
9
 
10
+ ## [0.0.5]
11
+ ### Added
12
+ - AocInput#process_each_line - Processes each line of the input data using the provided block
13
+ - Grid#includes_coords? - Returns `true` if the provided coordinates exist within the bounds of the grid
14
+ - Grid#beyond_grid? - Returns `true` if the provided coordinates exceed the bounds of the grid
15
+ - Grid#locate(value) - Returns the first coordinates within the grid containing the given value
16
+ - Grid#locate_all(value) - Returns an array of coordinates for any location within the grid containing the given value
17
+ - Grid#each_cell - Iterates over each cell in the grid
18
+
19
+ ### Changed
20
+ - Grid#cell now returns `nil` if the provided coordinates to not exist within the grid
21
+ - Grid#set_cell nwo returns `nil` if the provided coordinates to not exist within the grid
22
+
23
+ ### Fixed
24
+ - Grid#dup previously returned a new `Grid` instance with the same instance of the `@grid` array within it. Now `@grid` is a unique copy.
25
+
26
+ ## [0.0.4]
27
+ ### Added
28
+ - Grid class for working with two-dimensional arrays of data
29
+ - AocInput updated with convenience method for creating a Grid from input
30
+ - AocInput#sections added, for splitting input into multiple sections
31
+
10
32
  ## [0.0.3]
11
33
  ### Added
12
34
  - Characters `I` and `T` now supported by DotMatrix
@@ -23,6 +45,9 @@ Initial release.
23
45
  ### Added
24
46
  - Created `AocInput` class with initial helper methods
25
47
 
26
- [Unreleased]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.2...HEAD
48
+ [Unreleased]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.5...HEAD
49
+ [0.0.5]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.4...v0.0.5
50
+ [0.0.4]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.3...v0.0.4
51
+ [0.0.3]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.2...v0.0.3
27
52
  [0.0.2]: https://github.com/pacso/aoc_rb_helpers/compare/v0.0.1...v0.0.2
28
53
  [0.0.1]: https://github.com/pacso/aoc_rb_helpers
data/README.md CHANGED
@@ -58,13 +58,21 @@ X X X X X X X X X X X X X
58
58
  ```
59
59
  Into the string `CFLELOYFCS`.
60
60
 
61
+ ### [Grid](https://rubydoc.info/github/pacso/aoc_rb_helpers/Grid)
62
+ Provides helper methods for manipulating end querying two-dimensional grids.
63
+
64
+ ```ruby
65
+ grid = Grid.new([[0, 1], [2, 3]])
66
+ grid.rotate! # => #<Grid:0x0000ffff8f42f8f8 @grid=[[2, 0], [3, 1]]>
67
+ ```
68
+
61
69
  ## Examples
62
70
 
63
71
  Below are some examples of how you can use the features of this gem.
64
72
 
65
73
  ### Input manipulation
66
74
 
67
- This example solution for 2024 Day 1 shows how the convenience methdos can be used to format the puzzle input:
75
+ This example solution for 2024 Day 1 shows how the convenience methods can be used to format the puzzle input:
68
76
 
69
77
  ```ruby
70
78
  # frozen_string_literal: true
@@ -95,6 +103,14 @@ module Year2024
95
103
  end
96
104
  ```
97
105
 
106
+ Where you have different sections of input which need to be handled differently, you can quickly split them into independent instances of `AocInput`, such as with the pussle from 2024, Day 5:
107
+
108
+ ```ruby
109
+ page_rules, page_updates = aoc_input.sections
110
+ page_rules_data = page_rules.multiple_lines.columns_of_numbers("|").data
111
+ page_updates_data = page_updates.multiple_lines.columns_of_numbers(",").data
112
+ ```
113
+
98
114
  ### Decoding printed text
99
115
 
100
116
  ```ruby
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Provides input manipulation helper methods.
4
- # Methods are chainable, and directly modify the parsed view of the input data within the @data instance variable.
4
+ # Methods are chainable, and directly modify the parsed view of the input data within the +@data+ instance variable.
5
5
  #
6
- # Once manipulated as required, the input is accessable from #data
6
+ # Once manipulated as required, the input is accessable using the {#data} method.
7
7
  class AocInput
8
8
  # Returns a new AocInput initialized with the given puzzle input.
9
9
  #
@@ -24,8 +24,8 @@ class AocInput
24
24
 
25
25
  # Splits the input string into an array of lines.
26
26
  #
27
- # This method processes `@data` by splitting the input string into multiple lines,
28
- # removing trailing newline characters. It modifies `@data` directly and returns `self`
27
+ # This method processes +@data+ by splitting the input string into multiple lines,
28
+ # removing trailing newline characters. It modifies +@data+ directly and returns +self+
29
29
  # to enable method chaining.
30
30
  #
31
31
  # @return [AocInput] self
@@ -36,13 +36,33 @@ class AocInput
36
36
  self
37
37
  end
38
38
 
39
+ # Processes each line of the data using the provided block.
40
+ #
41
+ # This method applies the given block to each line in the +@data+ array,
42
+ # replacing the original +@data+ with the results of the block. The method
43
+ # returns +self+ to allow method chaining.
44
+ #
45
+ # Returns an enumerator if no block is given.
46
+ #
47
+ # @yieldparam line [Object, Array<Object>] a single line of the data being processed
48
+ # @yieldreturn [Object, Array<Object>] the result of processing the line
49
+ # @return [self] the instance itself, for method chaining
50
+ # @return [Enumerator] if no block is given
51
+ def process_each_line
52
+ return to_enum(__callee__) unless block_given?
53
+ @data = @data.map do |line|
54
+ yield line
55
+ end
56
+ self
57
+ end
58
+
39
59
  # Splits each string in the data array into an array of numbers.
40
60
  #
41
- # This method processes `@data` by splitting each string in the array using the specified delimiter,
42
- # then converting each resulting element to an integer. It modifies `@data` directly and enables
43
- # chaining by returning `self`.
61
+ # This method processes +@data+ by splitting each string in the array using the specified delimiter,
62
+ # then converting each resulting element to an integer. It modifies +@data+ directly and enables
63
+ # chaining by returning +self+.
44
64
  #
45
- # @param delimiter [String, nil] the delimiter to be passed to `String#split`
65
+ # @param delimiter [String, nil] the delimiter to be passed to +String#split+
46
66
  # @raise [RuntimeError] if {#multiple_lines} has not been called
47
67
  # @return [AocInput] self
48
68
  def columns_of_numbers(delimiter = nil)
@@ -57,7 +77,7 @@ class AocInput
57
77
  # Transposes the data array.
58
78
  #
59
79
  # This method can only be called after {columns_of_numbers}.
60
- # It directly modifies `@data` by transposing it and returns `self` to allow method chaining.
80
+ # It directly modifies +@data+ by transposing it and returns +self+ to allow method chaining.
61
81
  #
62
82
  # @raise [RuntimeError] if {columns_of_numbers} has not been called.
63
83
  # @return [AocInput] self
@@ -67,10 +87,10 @@ class AocInput
67
87
  self
68
88
  end
69
89
 
70
- # Sorts each array within the `@data` array.
90
+ # Sorts each array within the +@data+ array.
71
91
  #
72
- # This method processes `@data` by sorting each nested array in ascending order.
73
- # It directly modifies `@data` and returns `self` to enable method chaining.
92
+ # This method processes +@data+ by sorting each nested array in ascending order.
93
+ # It directly modifies +@data+ and returns +self+ to enable method chaining.
74
94
  #
75
95
  # @raise [RuntimeError] if {#columns_of_numbers} has not been called
76
96
  # @return [AocInput] self
@@ -80,6 +100,19 @@ class AocInput
80
100
  self
81
101
  end
82
102
 
103
+ # Returns a new +Grid+ object from the parsed input
104
+ # @return [Grid] the new grid
105
+ def to_grid
106
+ Grid.from_input(@raw_input)
107
+ end
108
+
109
+ # Returns a new +AocInput+ for each section of the raw input, split by the given delimiter.
110
+ # @param delimiter [String] the string used to split sections
111
+ # @return [Array<AocInput>] an array of new AocInput instances initialised with each section of the raw input from +self+
112
+ def sections(delimiter = "\n\n")
113
+ @sections = @raw_input.split(delimiter).map { |section_input| AocInput.new(section_input) }
114
+ end
115
+
83
116
  private
84
117
  def configure_method_access
85
118
  @can_call = {
@@ -0,0 +1,266 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides helper methods for manipulating end querying two-dimensional grids.
4
+ class Grid
5
+ # Returns a new {Grid} initialized with the given input.
6
+ #
7
+ # @param input [String] the unprocessed input text containing the grid
8
+ def self.from_input(input)
9
+ self.new(input.lines(chomp: true).map(&:chars))
10
+ end
11
+
12
+ # Returns a new {Grid} initialized with the provided two-dimensional array.
13
+ #
14
+ # @param grid [Array<Array<Object>>] the grid in a two-dimensional array
15
+ def initialize(grid)
16
+ @grid = grid
17
+ end
18
+
19
+ # Returns +true+ if the provided coordinates exceed the bounds of the grid; +false+ otherwise.
20
+ #
21
+ # @param row [Integer] the row index to test
22
+ # @param column [Integer] the column index to test
23
+ # @return [Boolean]
24
+ # @see #includes_coords?
25
+ def beyond_grid?(row, column)
26
+ !includes_coords?(row, column)
27
+ end
28
+
29
+ # Returns +true+ if the provided coordinates exist within the bounds of the grid; +false+ otherwise.
30
+ #
31
+ # @param row [Integer] the row index to test
32
+ # @param column [Integer] the column index to test
33
+ # @return [Boolean]
34
+ # @see #beyond_grid?
35
+ def includes_coords?(row, column)
36
+ row >= 0 && column >= 0 && row < @grid.length && column < @grid.first.length
37
+ end
38
+ alias_method(:within_grid?, :includes_coords?)
39
+
40
+ # Returns the value stored at coordinates +(row, column)+ within the grid.
41
+ #
42
+ # Returns +nil+ if the provided coordinates do not exist within the grid.
43
+ #
44
+ # Row and column numbers are zero-indexed.
45
+ #
46
+ # @param row [Integer] the row index of the desired cell
47
+ # @param column [Integer] the column index of the desired cell
48
+ # @return [Object] the value at the given coordinates within the grid
49
+ # @return [nil] if the given coordinates do not exist within the grid
50
+ # @see #set_cell
51
+ def cell(row, column)
52
+ return nil unless includes_coords?(row, column)
53
+ @grid[row][column]
54
+ end
55
+
56
+ # Updates the cell at coordinates +(row, column)+ with the object provided in +value+; returns the given object.
57
+ #
58
+ # Returns +nil+ if the provided coordinates do not exist within the grid.
59
+ #
60
+ # @param row [Integer] the row index of the cell you wish to update
61
+ # @param column [Integer] the column index of the cell you wish to update
62
+ # @param value [Object] the object to assign to the selected grid cell
63
+ # @return [Object] the given +value+
64
+ # @return [nil] if the provided coordinates do not exist within the grid
65
+ # @see #cell
66
+ def set_cell(row, column, value)
67
+ return nil unless includes_coords?(row, column)
68
+ @grid[row][column] = value
69
+ end
70
+
71
+ # Returns +true+ if the other grid has the same content in the same orientation, +false+ otherwise.
72
+ #
73
+ # grid = Grid.new([1, 2], [3, 4])
74
+ # grid == Grid.new([1, 2], [3, 4]) # => true
75
+ # grid == Grid.new([0, 1], [2, 3]) # => false
76
+ # grid == "non-grid object" # => false
77
+ #
78
+ # @param other [Grid] the grid you wish to compare with
79
+ # @return [Boolean]
80
+ def ==(other)
81
+ return false unless other.is_a?(self.class)
82
+ @grid == other.instance_variable_get(:@grid)
83
+ end
84
+
85
+ # Returns +true+ if the other grid can be rotated into an orientation where it is equal to +self+, +false+ otherwise.
86
+ #
87
+ # grid = Grid.new([1, 2], [3, 4])
88
+ # grid == Grid.new([1, 2], [3, 4]) # => true
89
+ # grid == Grid.new([3, 1], [4, 2]) # => true
90
+ # grid == Grid.new([1, 2], [4, 3]) # => false
91
+ # grid == "non-grid object" # => false
92
+ #
93
+ # @param other [Grid] the grid you wish to compare with
94
+ def matches_with_rotations?(other)
95
+ other.all_rotations.any? { |rotated| self == rotated }
96
+ end
97
+
98
+ # Returns an array of {Grid} objects in all possible rotations, copied from +self+.
99
+ # @return [Array<Grid>] an array containing four {Grid} objects, one in each possible rotation
100
+ def all_rotations
101
+ rotations = []
102
+ current_grid = self.dup
103
+
104
+ 4.times do
105
+ rotations << current_grid.dup
106
+ current_grid.rotate!
107
+ end
108
+
109
+ rotations
110
+ end
111
+
112
+ # Returns a new {Grid} as a copy of self.
113
+ # @return [Grid] a copy of +self+
114
+ def dup
115
+ self.class.new(@grid.map { |row| row.map { |cell| cell } })
116
+ end
117
+
118
+ # Updates +self+ with a rotated grid and returns +self+.
119
+ #
120
+ # Will rotate in a clockwise direction by default. Will rotate in an anticlockwise direction if passed
121
+ # a param which is not +:clockwise+.
122
+ #
123
+ # @param direction [Symbol]
124
+ # @return [self]
125
+ def rotate!(direction = :clockwise)
126
+ @grid = direction == :clockwise ? @grid.transpose.map(&:reverse) : @grid.map(&:reverse).transpose
127
+ self
128
+ end
129
+
130
+ # Calls the given block with each subgrid from +self+ with the size constraints provided; returns +self+.
131
+ #
132
+ # Returns an enumerator if no block is given
133
+ #
134
+ # @return [Enumerator] if no block is given.
135
+ # @return [self] after processing the provided block
136
+ # @yield [subgrid] calls the provided block with each subgrid as a new {Grid} object
137
+ # @yieldparam subgrid [Grid] a new {Grid} object containing a subgrid from the main grid
138
+ def each_subgrid(rows, columns)
139
+ return to_enum(__callee__, rows, columns) unless block_given?
140
+ @grid.each_cons(rows) do |rows|
141
+ rows[0].each_cons(columns).with_index do |_, col_index|
142
+ yield Grid.new(rows.map { |row| row[col_index, columns] })
143
+ end
144
+ end
145
+
146
+ self
147
+ end
148
+
149
+ # Returns an array containing all of the subgrids of the specified dimensions.
150
+ # @param rows [Integer] the number of rows each subgrid should contain.
151
+ # Must be greater than zero and less than or equal to the number of rows in the grid.
152
+ # @param columns [Integer] the number of columns each subgrid should contain.
153
+ # Must be greater than zero and less than or equal to the number of columns in the grid.
154
+ # @raise [ArgumentError] if the specified rows or columns are not {Integer} values, or exceed the grid's dimensions.
155
+ # @return [Array<Grid>]
156
+ def subgrids(rows, columns)
157
+ raise ArgumentError unless rows.is_a?(Integer) && rows > 0 && rows <= @grid.length
158
+ raise ArgumentError unless columns.is_a?(Integer) && columns > 0 && columns <= @grid.first.length
159
+ each_subgrid(rows, columns).to_a
160
+ end
161
+
162
+ # Returns the first coordinates within the grid containing the given value. Returns +nil+ if not found.
163
+ #
164
+ # If given an array of values, the first coordinate matching any of the given values will be returned.
165
+ #
166
+ # Searches the grid from top left (+[0, 0]+) to bottom right, by scanning each row.
167
+ #
168
+ # @param value [Object, Array<Object>] the value, or array of values, to search for.
169
+ # @return [Array<Integer>] if the value was located, its coordinates are returned in a 2-item array where:
170
+ # - The first item is the row index.
171
+ # - The second item is the column index.
172
+ # @return [nil] if the value was not located
173
+ def locate(value)
174
+ result = nil
175
+ if value.is_a? Array
176
+ value.each do |e|
177
+ result = locate(e)
178
+ break unless result.nil?
179
+ end
180
+ else
181
+ result = locate_value value
182
+ end
183
+ result
184
+ end
185
+
186
+ # Returns an array of coordinates for any location within the grid containing the given value.
187
+ #
188
+ # If given an array of values, the coordinates of any cell matching any of the given values will be returned.
189
+ #
190
+ # @param value [Object, Array<Object>] the value, or array of values, to search for.
191
+ # @return [Array<Array<Integer>>] an array of coordinates. Each coordinate is a 2-item array where:
192
+ # - The first item is the row index.
193
+ # - The second item is the column index.
194
+ def locate_all(value)
195
+ locations = []
196
+
197
+ if value.is_a? Array
198
+ @grid.each_with_index.select { |row, _r_index| value.any? { |el| row.include?(el) } }.each do |row, r_index|
199
+ row.each_with_index do |cell, c_index|
200
+ locations << [r_index, c_index] if value.include?(cell)
201
+ end
202
+ end
203
+ else
204
+ @grid.each_with_index.select { |row, _r_index| row.include?(value) }.each do |row, r_index|
205
+ row.each_with_index do |cell, c_index|
206
+ locations << [r_index, c_index] if cell == value
207
+ end
208
+ end
209
+ end
210
+
211
+ locations
212
+ end
213
+
214
+ # Iterates over each cell in the grid.
215
+ #
216
+ # When a block is given, passes the coordinates and value of each cell to the block; returns +self+:
217
+ # g = Grid.new([
218
+ # ["a", "b"],
219
+ # ["c", "d"]
220
+ # ])
221
+ # g.each_cell { |coords, value| puts "#{coords.inspect} => #{value}" }
222
+ #
223
+ # Output:
224
+ # [0, 0] => a
225
+ # [0, 1] => b
226
+ # [1, 0] => c
227
+ # [1, 1] => d
228
+ #
229
+ # When no block is given, returns a new Enumerator:
230
+ # g = Grid.new([
231
+ # [:a, "b"],
232
+ # [3, true]
233
+ # ])
234
+ # e = g.each_cell
235
+ # e # => #<Enumerator: #<Grid: @grid=[[\"a\", \"b\"], [\"c\", \"d\"]]>:each_cell>
236
+ # g1 = e.each { |coords, value| puts "#{coords.inspect} => #{value.class}: #{value}" }
237
+ #
238
+ # Output:
239
+ # [0, 0] => Symbol: a
240
+ # [0, 1] => String: b
241
+ # [1, 0] => Integer: 3
242
+ # [1, 1] => TrueClass: true
243
+ # @yieldparam coords [Array<Integer>] the coordinates of the cell in a 2-item array where:
244
+ # # - The first item is the row index.
245
+ # # - The second item is the column index.
246
+ # @yieldparam value [Object] the value stored within the cell
247
+ # @return [self]
248
+ def each_cell
249
+ return to_enum(__callee__) unless block_given?
250
+ @grid.each_with_index do |row, r_index|
251
+ row.each_with_index do |cell, c_index|
252
+ yield [[r_index, c_index], cell]
253
+ end
254
+ end
255
+ self
256
+ end
257
+
258
+ private
259
+
260
+ def locate_value(element)
261
+ row = @grid.index { |row| row.include?(element) }
262
+ return nil if row.nil?
263
+ column = @grid[row].index(element)
264
+ [row, column]
265
+ end
266
+ end
@@ -1,3 +1,3 @@
1
1
  module AocRbHelpers
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aoc_rb_helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Pascoe
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-12-02 00:00:00.000000000 Z
11
+ date: 2024-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aoc_rb
@@ -70,6 +70,7 @@ files:
70
70
  - lib/aoc_rb_helpers.rb
71
71
  - lib/aoc_rb_helpers/aoc_input.rb
72
72
  - lib/aoc_rb_helpers/dot_matrix.rb
73
+ - lib/aoc_rb_helpers/grid.rb
73
74
  - lib/aoc_rb_helpers/solution/input_handling.rb
74
75
  - lib/aoc_rb_helpers/version.rb
75
76
  homepage: https://github.com/pacso/aoc_rb_helpers