lib/tap/support/combinator.rb in bahuvrihi-tap-0.10.6 vs lib/tap/support/combinator.rb in bahuvrihi-tap-0.10.7
- old
+ new
@@ -1,23 +1,66 @@
-module Tap
+require 'enumerator'
+
+module Tap
module Support
+
+ # Combinator provides a method for iterating over all combinations
+ # of items in the input sets.
+ #
+ # c = Combinator.new [1,2], [3,4]
+ # c.to_a # => [[1,3], [1,4], [2,3], [2,4]]
+ #
+ # Combinators can take any object that responds to :each as an
+ # input set; normally arrays are used.
+ #
+ # === Implementation
+ #
+ # Combinator iteratively combines each element from the first set (a)
+ # with each element from the second set (b). When more than two sets
+ # are given, the Combinator bundles all but the first set into a new
+ # Combinator, which then acts as the second set.
+ #
+ # c = Combinator.new [1,2], [3,4], [5,6]
+ # c.a # => [[1],[2]]
+ # c.b.class # => Combinator
+ # c.b.a # => [[3],[4]]
+ # c.b.b # => [[5],[6]]
+ #
+ # Note that internally each item in a set is stored as a single-item
+ # array; the arrays are added during combination. Thus when
+ # iterating, the combinations are calculated like:
+ #
+ # ([1] + [3]) + [5] # => [1,3,5]
+ #
+ # This is probably not the fastest implementation, but it works.
class Combinator
- attr_reader :a, :b
+ include Enumerable
+
+ # The first set.
+ attr_reader :a
+
+ # The second set.
+ attr_reader :b
+ # Creates a new Combinator. Input sets must be an Array, or nil.
+ # Within the Array any objects are allowed for combination.
def initialize(*sets)
@a = make_set(sets.shift)
@b = make_set(*sets)
end
+ # Returns the sets used to initialize the Combinator.
def sets
- sets_in(@a) + sets_in(@b)
+ sets_in(a) + sets_in(b)
end
+ # True if length is zero.
def empty?
- @a.empty? && @b.empty?
+ a.empty? && b.empty?
end
+ # Returns the number of combinations returned by each.
def length
case
when !(@a.empty? || @b.empty?)
@a.length * @b.length
when @a.empty?
@@ -25,49 +68,58 @@
when @b.empty?
@a.length
end
end
+ # Passes each combination as an array to the input block.
def each
case
when !(@a.empty? || @b.empty?)
- @a.each do |*a|
- @b.each do |*b|
- yield(*(a + b))
+ @a.each do |a|
+ @b.each do |b|
+ yield(a + b)
end
end
when @a.empty?
- @b.each {|*b| yield(*b) }
+ @b.each {|b| yield(b) }
when @b.empty?
- @a.each {|*a| yield(*a) }
+ @a.each {|a| yield(a) }
end
end
- def collect
- cc = []
- each do |*c|
- cc << (block_given? ? yield(*c) : c)
- end
- cc
+ private
+
+ # makes a Combinator out of multiple sets or collects the
+ # objects of a single set as arrays:
+ #
+ # make_set([1,2,3], [4,5,6]) # => Combinator.new([1,2,3], [4,5,6])
+ # make_set([1,2,3]) # => [[1],[2],[3]]
+ #
+ def make_set(*sets) # :nodoc:
+ # recieves an array of arrays or combinators
+ return Combinator.new(*sets) if sets.length > 1
+ return sets if sets.empty?
+
+ set = sets[0]
+ return [] if set == nil
+
+ unless set.respond_to?(:each)
+ raise ArgumentError, "does not respond to each: #{set}"
+ end
+
+ # recursively arrayifies each element
+ arrayified_set = []
+ set.each {|s| arrayified_set << [s]}
+ arrayified_set
end
- protected
-
- def sets_in(set)
- set.kind_of?(Array) ? (set.empty? ? [] : [set]) : set.sets
- end
-
- def make_set(*sets)
- return Combinator.new(*sets) if sets.length > 1
- return [] if sets.empty?
-
- set = sets.first
- case set
- when Combinator then set
- when Array then set
- when nil then []
- else [set]
+ # basically the reverse of make_set
+ def sets_in(set) # :nodoc:
+ case set
+ when Combinator then set.sets
+ when Array then set.empty? ? [] : [set.collect {|s| s[0]}]
end
end
+
end
end
end
\ No newline at end of file