Skip to content

Instantly share code, notes, and snippets.

@postmodern
Created October 1, 2010 07:50
Show Gist options
  • Save postmodern/605891 to your computer and use it in GitHub Desktop.
Save postmodern/605891 to your computer and use it in GitHub Desktop.
Adds Haskell style list comprehensions to the Ruby Array
class Array
#
# Iterates over each permutation of the enumerable values within the
# {Array}.
#
# @yield [list]
# The given block will be passed each permutation of the enumerable
# values in the {Array}.
#
# @yieldparam [Array] list
# A permutation of the enumerable values within the {Array}.
#
# @return [Enumerator]
# If no block is given, an enumerator object will be returned.
#
# @example
# [(1..5),(1..4),(1..3)].comprehension.to_a
# # => [
# [1, 1, 1], [1, 1, 2], [1, 1, 3],
# [1, 2, 1], [1, 2, 2], [1, 2, 3],
# [1, 3, 1], [1, 3, 2], [1, 3, 3],
# [1, 4, 1], [1, 4, 2], [1, 4, 3],
# [2, 1, 1], [2, 1, 2], [2, 1, 3],
# [2, 2, 1], [2, 2, 2], [2, 2, 3],
# [2, 3, 1], [2, 3, 2], [2, 3, 3],
# [2, 4, 1], [2, 4, 2], [2, 4, 3],
# [3, 1, 1], [3, 1, 2], [3, 1, 3],
# [3, 2, 1], [3, 2, 2], [3, 2, 3],
# [3, 3, 1], [3, 3, 2], [3, 3, 3],
# [3, 4, 1], [3, 4, 2], [3, 4, 3],
# [4, 1, 1], [4, 1, 2], [4, 1, 3],
# [4, 2, 1], [4, 2, 2], [4, 2, 3],
# [4, 3, 1], [4, 3, 2], [4, 3, 3],
# [4, 4, 1], [4, 4, 2], [4, 4, 3],
# [5, 1, 1], [5, 1, 2], [5, 1, 3],
# [5, 2, 1], [5, 2, 2], [5, 2, 3],
# [5, 3, 1], [5, 3, 2], [5, 3, 3],
# [5, 4, 1], [5, 4, 2], [5, 4, 3]
# ]
#
# @note
# This currently will not run under Ruby 1.8, due to the fact that the
# Ruby 1.8 `Enumerable` class not provide the `#peek` method. Additionally,
# the `Enumerator` implementation in Ruby 1.8 is much slower than that of
# Ruby 1.9. Please upgrade to 1.9.2 for the best Ruby experience.
#
def comprehension
return enum_for(:comprehension) unless block_given?
ranges = self.map do |range|
if range.kind_of?(Enumerable)
range
elsif range.kind_of?(Proc)
Enumerator.new(&range)
else
(range..range)
end
end
cycles = ranges.map { |range| range.cycle }
loop do
yield cycles.map { |cycle| cycle.peek }
(cycles.length - 1).downto(0) do |index|
cycles[index].next
break unless cycles[index].peek == ranges[index].first
return nil if index == 0
end
end
end
end
@dkubb
Copy link

dkubb commented Jan 17, 2012

I wonder if there are nice ways to include predicates in this, or is it already there and my eyes glossed over it?

@postmodern
Copy link
Author

Current version of this is in combinatorics.

I suppose I could add support for lambdas by passing them the half-built Array.

[(0..255), ->(a) { a[0].chr }]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment