Class: Grid

Inherits:
Object
  • Object
show all
Defined in:
lib/aoc_rb_helpers/grid.rb

Overview

Provides helper methods for manipulating end querying two-dimensional grids.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(grid) ⇒ Grid

Returns a new Grid initialized with the provided two-dimensional array.

Parameters:

  • grid (Array<Array<Object>>)

    the grid in a two-dimensional array



15
16
17
# File 'lib/aoc_rb_helpers/grid.rb', line 15

def initialize(grid)
  @grid = grid
end

Class Method Details

.from_input(input) ⇒ Object

Returns a new Grid initialized with the given input.

Parameters:

  • input (String)

    the unprocessed input text containing the grid



8
9
10
# File 'lib/aoc_rb_helpers/grid.rb', line 8

def self.from_input(input)
  self.new(input.lines(chomp: true).map(&:chars))
end

Instance Method Details

#==(other) ⇒ Boolean

Returns true if the other grid has the same content in the same orientation, false otherwise.

grid = Grid.new([1, 2], [3, 4])
grid == Grid.new([1, 2], [3, 4]) # => true
grid == Grid.new([0, 1], [2, 3]) # => false
grid == "non-grid object" # => false

Parameters:

  • other (Grid)

    the grid you wish to compare with

Returns:

  • (Boolean)


81
82
83
84
# File 'lib/aoc_rb_helpers/grid.rb', line 81

def ==(other)
  return false unless other.is_a?(self.class)
  @grid == other.instance_variable_get(:@grid)
end

#all_rotationsArray<Grid>

Returns an array of Grid objects in all possible rotations, copied from self.

Returns:

  • (Array<Grid>)

    an array containing four Grid objects, one in each possible rotation



101
102
103
104
105
106
107
108
109
110
111
# File 'lib/aoc_rb_helpers/grid.rb', line 101

def all_rotations
  rotations = []
  grid = self.dup

  4.times do
    rotations << grid.dup
    grid.rotate!
  end

  rotations
end

#beyond_grid?(row, column) ⇒ Boolean

Returns true if the provided coordinates exceed the bounds of the grid; false otherwise.

Parameters:

  • row (Integer)

    the row index to test

  • column (Integer)

    the column index to test

Returns:

  • (Boolean)

See Also:



25
26
27
# File 'lib/aoc_rb_helpers/grid.rb', line 25

def beyond_grid?(row, column)
  !includes_coords?(row, column)
end

#cell(row, column) ⇒ Object?

Returns the value stored at coordinates (row, column) within the grid.

Returns nil if the provided coordinates do not exist within the grid.

Row and column numbers are zero-indexed.

Parameters:

  • row (Integer)

    the row index of the desired cell

  • column (Integer)

    the column index of the desired cell

Returns:

  • (Object)

    the value at the given coordinates within the grid

  • (nil)

    if the given coordinates do not exist within the grid

See Also:



52
53
54
55
# File 'lib/aoc_rb_helpers/grid.rb', line 52

def cell(row, column)
  return nil unless includes_coords?(row, column)
  @grid[row][column]
end

#dupGrid

Returns a new Grid as a copy of self.

Returns:

  • (Grid)

    a copy of self



115
116
117
# File 'lib/aoc_rb_helpers/grid.rb', line 115

def dup
  self.class.new Marshal.load(Marshal.dump(@grid))
end

#each_cell {|coords, value| ... } ⇒ Grid, Enumerator

Iterates over each cell in the grid.

When a block is given, passes the coordinates and value of each cell to the block; returns self:

g = Grid.new([
      ["a", "b"],
      ["c", "d"]
    ])
g.each_cell { |coords, value| puts "#{coords.inspect} => #{value}" }

Output:

[0, 0] => a
[0, 1] => b
[1, 0] => c
[1, 1] => d

When no block is given, returns a new Enumerator:

g = Grid.new([
      [:a, "b"],
      [3, true]
    ])
e = g.each_cell
e # => #<Enumerator: #<Grid: @grid=[[\"a\", \"b\"], [\"c\", \"d\"]]>:each_cell>
g1 = e.each { |coords, value| puts "#{coords.inspect} => #{value.class}: #{value}" }

Output:

[0, 0] => Symbol: a
[0, 1] => String: b
[1, 0] => Integer: 3
[1, 1] => TrueClass: true

Yield Parameters:

  • coords (Array<Integer>)

    the coordinates of the cell in a 2-item array where: # - The first item is the row index. # - The second item is the column index.

  • value (Object)

    the value stored within the cell

Returns:

  • (Grid)

    if given a block, returns self after calling block for each cell

  • (Enumerator)

    if no block is given



250
251
252
253
254
255
256
257
258
# File 'lib/aoc_rb_helpers/grid.rb', line 250

def each_cell
  return to_enum(__callee__) unless block_given?
  @grid.each_with_index do |row, r_index|
    row.each_with_index do |cell, c_index|
      yield [[r_index, c_index], cell]
    end
  end
  self
end

#each_cell! {|value| ... } ⇒ Grid, Enumerator Also known as: format_cells

Calls the block, if given, with each cell value; replaces the cell in the grid with the block’s return value:

Returns a new Enumerator if no block given

Yield Parameters:

  • value (Object)

    the value stored within the cell

Returns:

  • (Grid)

    if given a block, returns self after calling block for each cell

  • (Enumerator)

    if no block is given



267
268
269
270
271
272
273
274
275
# File 'lib/aoc_rb_helpers/grid.rb', line 267

def each_cell!
  return to_enum(__callee__) unless block_given?
  @grid.each_with_index do |row, r_index|
    row.each_with_index do |cell, c_index|
      @grid[r_index][c_index] = yield cell
    end
  end
  self
end

#each_subgrid(rows, columns) {|subgrid| ... } ⇒ Grid, Enumerator

Calls the given block with each subgrid from self with the size constraints provided; returns self.

Returns an enumerator if no block is given

Yields:

  • (subgrid)

    calls the provided block with each subgrid as a new Grid object

Yield Parameters:

  • subgrid (Grid)

    a new Grid object containing a subgrid from the main grid

Returns:

  • (Grid)

    if given a block, returns self after calling block for each subgrid

  • (Enumerator)

    if no block is given.



139
140
141
142
143
144
145
146
147
148
# File 'lib/aoc_rb_helpers/grid.rb', line 139

def each_subgrid(rows, columns)
  return to_enum(__callee__, rows, columns) unless block_given?
  @grid.each_cons(rows) do |rows|
    rows[0].each_cons(columns).with_index do |_, col_index|
      yield Grid.new(rows.map { |row| row[col_index, columns] })
    end
  end

  self
end

#includes_coords?(row, column) ⇒ Boolean Also known as: within_grid?

Returns true if the provided coordinates exist within the bounds of the grid; false otherwise.

Parameters:

  • row (Integer)

    the row index to test

  • column (Integer)

    the column index to test

Returns:

  • (Boolean)

See Also:



35
36
37
# File 'lib/aoc_rb_helpers/grid.rb', line 35

def includes_coords?(row, column)
  row >= 0 && column >= 0 && row < @grid.length && column < @grid.first.length
end

#locate(value) ⇒ Array<Integer>?

Returns the first coordinates within the grid containing the given value. Returns nil if not found.

If given an array of values, the first coordinate matching any of the given values will be returned.

Searches the grid from top left (+[0, 0]+) to bottom right, by scanning each row.

Parameters:

  • value (Object, Array<Object>)

    the value, or array of values, to search for.

Returns:

  • (Array<Integer>)

    if the value was located, its coordinates are returned in a 2-item array where:

    • The first item is the row index.

    • The second item is the column index.

  • (nil)

    if the value was not located



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/aoc_rb_helpers/grid.rb', line 174

def locate(value)
  result = nil
  if value.is_a? Array
    value.each do |e|
      result = locate(e)
      break unless result.nil?
    end
  else
    result = locate_value value
  end
  result
end

#locate_all(value) ⇒ Array<Array<Integer>>

Returns an array of coordinates for any location within the grid containing the given value.

If given an array of values, the coordinates of any cell matching any of the given values will be returned.

Parameters:

  • value (Object, Array<Object>)

    the value, or array of values, to search for.

Returns:

  • (Array<Array<Integer>>)

    an array of coordinates. Each coordinate is a 2-item array where:

    • The first item is the row index.

    • The second item is the column index.



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/aoc_rb_helpers/grid.rb', line 195

def locate_all(value)
  locations = []

  if value.is_a? Array
    @grid.each_with_index.select { |row, _r_index| value.any? { |el| row.include?(el) } }.each do |row, r_index|
      row.each_with_index do |cell, c_index|
        locations << [r_index, c_index] if value.include?(cell)
      end
    end
  else
    @grid.each_with_index.select { |row, _r_index| row.include?(value) }.each do |row, r_index|
      row.each_with_index do |cell, c_index|
        locations << [r_index, c_index] if cell == value
      end
    end
  end

  locations
end

#matches_with_rotations?(other) ⇒ Boolean

Returns true if the other grid can be rotated into an orientation where it is equal to self, false otherwise.

grid = Grid.new([1, 2], [3, 4])
grid == Grid.new([1, 2], [3, 4]) # => true
grid == Grid.new([3, 1], [4, 2]) # => true
grid == Grid.new([1, 2], [4, 3]) # => false
grid == "non-grid object" # => false

Parameters:

  • other (Grid)

    the grid you wish to compare with

Returns:

  • (Boolean)


95
96
97
# File 'lib/aoc_rb_helpers/grid.rb', line 95

def matches_with_rotations?(other)
  other.all_rotations.any? { |rotated| self == rotated }
end

#neighbours(row, column, cardinal: true, ordinal: false) ⇒ Array<Array<Integer>>

For the given position indicated by the row and column provided, returns an array of coordinates which are direct neighbours. The returned coordinates are in clockwise order starting directly above the given cell:

g = Grid.new([
      [0, 1, 2, 3],
      [4, 5, 6, 7],
      [8, 9, 10, 11]
    ])
g.neighbours(1, 1) # => [[0, 1], [1, 2], [2, 1], [1, 0]]

If the keyword argument allow_diagonal: true is provided, diagonally accessible neighbours will also be included:

g = Grid.new([
      [0, 1, 2, 3],
      [4, 5, 6, 7],
      [8, 9, 10, 11]
    ])
g.neighbours(1, 1) # => [[0, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0], [1, 0], [0, 0]]

If provided a block, each neighbour’s cell value is yielded to the block, and only those neighbours for which the block returns a truthy value will be returned in the results:

g = Grid.new([
      [0, 1, 2, 3],
      [4, 5, 6, 7],
      [8, 9, 10, 11]
    ])
g.neighbours(1, 2) { |cell| cell.even? } # => [[0, 2], [2, 2]]
g.neighbours(1, 2, allow_diagonal: true) { |cell| cell <= 5 } # => [[0, 2], [0, 3], [1, 1], [0, 1]]

Parameters:

  • row (Integer)

    the row index of the starting cell

  • column (Integer)

    the column index of the starting cell

  • cardinal (Boolean) (defaults to: true)

    permits the direct north/east/south/west directions

  • ordinal (Boolean) (defaults to: false)

    permits diagonal north-east/south-east/south-west/north-west directions

Returns:

  • (Array<Array<Integer>>)

    an array of coordinates. Each coordinate is a 2-item array where:

    • The first item is the row index.

    • The second item is the column index.



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/aoc_rb_helpers/grid.rb', line 313

def neighbours(row, column, cardinal: true, ordinal: false)
  possible_neighbours = []
  possible_neighbours << [row - 1, column] if cardinal
  possible_neighbours << [row - 1, column + 1] if ordinal
  possible_neighbours << [row, column + 1] if cardinal
  possible_neighbours << [row + 1, column + 1] if ordinal
  possible_neighbours << [row + 1, column] if cardinal
  possible_neighbours << [row + 1, column - 1] if ordinal
  possible_neighbours << [row, column - 1] if cardinal
  possible_neighbours << [row - 1, column - 1] if ordinal

  valid_neighbours = possible_neighbours.select { |r, c| includes_coords?(r, c) }

  if block_given?
    valid_neighbours.select { |r, c| yield cell(r, c) }
  else
    valid_neighbours
  end
end

#rotate!(direction = :clockwise) ⇒ self

Updates self with a rotated grid and returns self.

Will rotate in a clockwise direction by default. Will rotate in an anticlockwise direction if passed a param which is not :clockwise.

Parameters:

  • direction (Symbol) (defaults to: :clockwise)

Returns:

  • (self)


126
127
128
129
# File 'lib/aoc_rb_helpers/grid.rb', line 126

def rotate!(direction = :clockwise)
  @grid = direction == :clockwise ? @grid.transpose.map(&:reverse) : @grid.map(&:reverse).transpose
  self
end

#set_cell(row, column, value) ⇒ Object?

Updates the cell at coordinates (row, column) with the object provided in value; returns the given object.

Returns nil if the provided coordinates do not exist within the grid.

Parameters:

  • row (Integer)

    the row index of the cell you wish to update

  • column (Integer)

    the column index of the cell you wish to update

  • value (Object)

    the object to assign to the selected grid cell

Returns:

  • (Object)

    the given value

  • (nil)

    if the provided coordinates do not exist within the grid

See Also:



67
68
69
70
# File 'lib/aoc_rb_helpers/grid.rb', line 67

def set_cell(row, column, value)
  return nil unless includes_coords?(row, column)
  @grid[row][column] = value
end

#subgrids(rows, columns) ⇒ Array<Grid>

Returns an array containing all of the subgrids of the specified dimensions.

Parameters:

  • rows (Integer)

    the number of rows each subgrid should contain. Must be greater than zero and less than or equal to the number of rows in the grid.

  • columns (Integer)

    the number of columns each subgrid should contain. Must be greater than zero and less than or equal to the number of columns in the grid.

Returns:

Raises:

  • (ArgumentError)

    if the specified rows or columns are not Integer values, or exceed the grid’s dimensions.



157
158
159
160
161
# File 'lib/aoc_rb_helpers/grid.rb', line 157

def subgrids(rows, columns)
  raise ArgumentError unless rows.is_a?(Integer) && rows > 0 && rows <= @grid.length
  raise ArgumentError unless columns.is_a?(Integer) && columns > 0 && columns <= @grid.first.length
  each_subgrid(rows, columns).to_a
end