Created
January 26, 2016 03:27
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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