Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save romaimperator/6859ac58d00cfad57da7 to your computer and use it in GitHub Desktop.
Save romaimperator/6859ac58d00cfad57da7 to your computer and use it in GitHub Desktop.
This is a fairly complicated example showing off how cool Ruby's yield and enumerators.
# Below is a complex example of using yielding methods to make a nice interface to use. As you
# can see in RankList#fill!, we don't need to care or check if the location really is empty.
# We also don't need to do the calculation to find the next location to insert at. We can
# iterate through each available location until we reach the end. A cool side effect is that
# we can use any of the enumerable methods on the returned enumerator from #fill_locations
# such as printing the positions of all of the empty spaces by doing:
# p alignment_strategy.fill_locations(@the_grid).to_a
module AlignmentStrategy
class Left
# rank_list: the multi-dimensional array of model locations
# blank_value: the value that represents a location without a model
def self.fill_locations(rank_list, blank_value=nil)
# This magical line will create an enumerator out of this function passing through the two arguments
# if this method doesn't get a block passed to it. It lets you say Left.fill_locations(rank_list).size
# to compute the number of empty locations left in the rank_list.
return to_enum(__callee__, rank_list, blank_value) unless block_given?
# Here we iterate through each rank and each file in each rank
rank_list.each_with_index do |rank, row|
rank.each_with_index do |value, column|
# Is this location blank? Yield its location if it is
if value == blank_value
# These numbers are incremented by one because I decided to number
# the ranks and files starting with one rather than zero.
yield [row + 1, column + 1]
end
end
end
end
# This function does the reverse of fill_locations, it iterates backwards to make it
# easy to find the first location to remove models from. Not the fanciest of implementations
# but I'm including it for symmetry.
def self.remove_locations(rank_list, removal_value)
return to_enum(__callee__, rank_list, removal_value) unless block_given?
row = rank_list.size - 1
while row >= 0
rank = rank_list[row]
column = rank.size - 1
while column >= 0
value = rank[column + 1]
if value == removal_value
yield [row + 1, column + 1]
end
column -= 1
end
row -= 1
end
end
end
end
class RankList
# This method fills the ranks with the new_value up to number_to_fill times.
# @the_grid is the instance variable containing the multi-dimensional array.
def fill!(new_value, number_to_fill)
# Using our alignment strategy, iterate through locations yielding the rank
# and file number of the next open position.
alignment_strategy.fill_locations(@the_grid) do |rank, file|
# While we still need to fill more locations...
if number_to_fill > 0
@the_grid[rank][file] = new_value
number_to_fill -= 1
else
# If we're done filling break out of the enumeration.
break
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment