module Enumerable
Public Class Methods
@return [Array] the cartesian product of the enumerations.
@see en.wikipedia.org/wiki/Cartesian_product
This is the fastest implementation we have found. It returns results in typical order.
@author Thomas Hafner @see www.ruby-forum.com/topic/95519
For our benchmarks, we also compared these:
-
By William James, www.ruby-forum.com/topic/95519
-
By Brian Schröäer, blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/151857
# File lib/sixarm_ruby_ramp/enumerable.rb, line 198 def self.cartesian_product(*enums) result = [[]] while [] != enums t, result = result, [] b, *enums = enums t.each do |a| b.each do |n| result << a + [n] end end end result end
Public Instance Methods
@example
enum.bisect {|obj| block} => array of positives, array of negatives
@return [Array<Array<Object>] an array of two arrays:
the first array is the elements for which block is true, the second array is the elements for which block is false or nil.
# File lib/sixarm_ruby_ramp/enumerable.rb, line 112 def bisect accept=[] reject=[] each{|item| if yield(item) accept << item else reject << item end } return accept,reject end
Calculate the cartesian product.
# File lib/sixarm_ruby_ramp/enumerable.rb, line 214 def cartesian_product(*enums) Enumerable.cartesian_product(self,*enums) end
Get each element at a given index or indices.
Example: use an index.
["a", "b", "c", "d", "e"].each_at(1) #=> "b"
Example: use an index that is negative when size is known.
["a", "b", "c", "d", "e"].each_at(-1) => "e"
Example: use a range.
["a", "b", "c", "d", "e"].each_at(1..3) => "b", "c", "d"
Example: use a range that has negatives when size is known.
["a", "b", "c", "d", "e"].each_at(-3..-1) => "c", "d", "e"
Example: use any object that responds to each or include?.
["a", "b", "c", "d", "e"].each_at([4, 2, -2]) => "e", "c", "d"
# File lib/sixarm_ruby_ramp/enumerable/each.rb, line 30 def each_at(filter) filter, optimize_for_whole_numbers = each_at_normalize_filter(filter) # Handle variations. # # Can we call self#at? # # * Yes: look up a self element by index. # * No: iterate on self, comparing the loop count to filter target. # # Can we call filter#each? # # * Yes: iterate on the filter elements. # * No: iterate on self, comparing the loop count to filter#include? # # Can we optimize for whole numbers? # # * Yes: we know that all filter targets are whole numbers. # * No: we must convert the filter target to an index size each time. # if self.respond_to?(:at) && filter.respond_to?(:each) if optimize_for_whole_numbers # each_at_strategy_with_self_at_and_filter_each_and_whole_numbers(filter) filter.each{|i| yield(at(i)) } else # each_at_strategy_with_self_at_and_filter_each(filter) filter.each{|i| yield(at(i >= 0 ? i : i + self.size)) } end elsif filter.respond_to?(:include?) # each_at_strategy_with_count(filter) i = 0 _size = respond_to?(:size) ? size : nil each{|e| yield(e) if (filter.include?(i) || (_size && filter.include(i - _size))) i += 1 } else raise ArgumentError end end
Normalize the filter to make it respond to each and include?.
If we can guarantee the filter is all whole numbers, then subsequent method calls can optimize the index lookup, by looking for the whole numbers instead of negative numbers.
# File lib/sixarm_ruby_ramp/enumerable/each.rb, line 131 def each_at_normalize_filter(filter) case filter when Numeric return [filter.to_i], filter >= 0 when Range if filter.first < 0 || filter.last < 0 min = filter.first.to_i; min += size if min < 0 max = filter.last.to_i; max += size if max < 0 max -= 1 if filter.exclude_end? filter = min..max end return filter, true else return filter, false end end
Implement each_at
by using a strategy with full optimization.
When each_at
tests that both self#at is available and filter#each is available, and can test that all indexes are whole numbers, then each_at
calls this strategy.
# File lib/sixarm_ruby_ramp/enumerable/each.rb, line 119 def each_at_strategy_with_optimization_max(filter) filter.each{|i| yield(at(i)) } end
Implement each_at
by using a strategy with some optimization.
When each_at
tests that both self#at is available and filter#each is available, yet cannot test that all indexes are whole numbers, then each_at
calls this strategy.
# File lib/sixarm_ruby_ramp/enumerable/each.rb, line 107 def each_at_strategy_with_optimization_min(filter) filter.each{|i| yield(at(i >= 0 ? i : i + self.size)) } end
Implement each_at
by using a strategy with no optimization.
When each_at
tests that either self#at is unavailable or filter#each is unavailable, then each_at
calls this strategy.
This strategy uses a loop counter and iteration on the self elements, and each iteration, test whether the counter is in the filter.
This strategy is the slowest, and for the worst-case need. This strategy is rarely needed in the wild.
# File lib/sixarm_ruby_ramp/enumerable/each.rb, line 86 def each_at_strategy_with_optimization_off(filter) i = 0 if respond_to?(:size) each{|e| yield(e) if (filter.include?(i) || (size && filter.include?(i - size))) i += 1 } else each{|e| yield(e) if (filter.include?(i)) i += 1 } end end
Convert the enumerable to a hash by mapping each item to a pair [item, new item]
@return [Hash<Object,Object>] a hash of the enumerable's items
@example
strings = ["red","blue","green"] strings.hash_by{|a| [a.size, a.upcase]} => {3 => "RED", 4 => "BLUE", 5 => "GREEN"}
@see index_by
# File lib/sixarm_ruby_ramp/enumerable.rb, line 94 def hash_by map{|item| yield(item)}.to_h end
Convert the enumerable to a hash by mapping each item to a pair [index ,item]
@return [Hash<Integer,Object>] a hash of the enumerable's items
@example
strings = ["red","blue","green"] strings.index_by{|a| a.size]} => {3 => "red", 4 => "blue", 5 => "green"}
Rails has this method.
From stackoverflow.com/questions/412771/cleanest-way-to-create-a-hash-from-an-array
@see hash_by
# File lib/sixarm_ruby_ramp/enumerable.rb, line 79 def index_by inject({}) {|hash, elem| hash.merge!(yield(elem) => elem) } end
@return [Boolean] true if this enum intersects another enum.
A developer may want to optimize this implementation for other classes, such as detecting whether a range intersects another range simply by comparing the ranges' min/max values.
@example
['a','b','c'].intersect?(['c','d','e'] => true ['a','b','c'].intersect?(['d','e','f'] => false
# File lib/sixarm_ruby_ramp/enumerable.rb, line 180 def intersect?(enum) return enum.any?{|item| self.include?(item)} end
Shortcut to Array#join
to concatenate the items into a string
@example
["foo", "goo", "hoo"].join => "foogoohoo"
@return [String] concatenated string
@see Array#join
# File lib/sixarm_ruby_ramp/enumerable.rb, line 160 def join(*op) to_a.join(*op) end
Map each item => item.id
@return [Enumerable<Object>] an list of each item.id
@example
users = User.find(:all) users.map_id => [1,2,3,4,...]
A typical use is to convert a list of ActiveRecord items to a list of id items.
This method is a faster way to get the same results as items.map(&:id)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 15 def map_id map{|item| item.id} end
Map each item => item.to_a
@return [Enumberable<Array<Object>>] a list of each item.to_a
@example
hashes = [{1=>2, 3=>4},{5=>6, 7=>8}] hashes.map_to_a => [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
This method is a faster way to get the same results as items.map(&:to_a)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 29 def map_to_a map{|item| item.to_a} end
Map each item => item.to_f
@return [Enumerable<Float>] a list of each item.to_f
@example
strings = ["1","2","3"] strings.map_to_f => [1.0, 2.0, 3.0]
A typical use is to convert a list of String
items to a list of float items.
This method is a fast way to get the same results as items.map(&:to_f)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 45 def map_to_f map{|item| item.to_f} end
Map each item => item.to_i
@return [Enumerable<Integer>] a list of each item.to_i
@example
strings = ["1","2","3"] strings.map_to_i => [1, 2, 3]
A typical use is to convert a list of String
items to a list of integer items.
This method is a fast way to get the same results as items.map(&:to_i)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 61 def map_to_i map{|item| item.to_i} end
Map each item => item.to_s
@return [Enumerable<String>] a list of each item.to_s
@example
numbers = [1, 2, 3] numbers.map_to_s => ["1", "2", "3"]
A typical use is to convert a list of Numeric
items to a list of String
items.
This method is a fast way to get the same results as items.map(&:to_s)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 77 def map_to_s map{|item| item.to_s} end
Map each item => item.to_sym
@return [Enumerable<Symbol>] a list of each item.to_sym
@example
strings = ["foo", "goo", "hoo"] strings.map_to_sym => [:foo, :goo, :hoo]
A typical use is to convert a list of Object
items to a list of Symbol items.
This method is a fast way to get the same results as items.map(&:to_sym)
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 93 def map_to_sym map{|item| item.to_sym} end
Map each item and its index => a new output
@return [Enumerable<Object>] an list of each item transformed by the block @see Enumerable#map @see Enumerable#each_with_index
@example
strings = ["a", "b", "c"] strings.map_with_index{|string,index| "#{string}#{index}"} => ["a0, "b1", "c3"]
# File lib/sixarm_ruby_ramp/enumerable/map.rb, line 108 def map_with_index index=-1 map{|item| index+=1; yield(item,index)} end
@example
enum.mutex? {|obj| block} => true iff block is not false or nil, zero or one time
@return boolean true iff block is not false or nil, zero or one time
# File lib/sixarm_ruby_ramp/enumerable.rb, line 137 def mutex? num = 0 each{|item| if yield(item) num += 1 if num > 1 then return false end end } return true end
@example
enum.nitems?(n) {| obj | block } => true iff the block is not false or nil num times
@return [Boolean] true iff the block is not false or nil num times
# File lib/sixarm_ruby_ramp/enumerable/nitems.rb, line 9 def nitems?(n) num = 0 each{|item| if yield(item) num+=1 if num > n then return false end end } return num==n end
@example
enum.nitems_until {| obj | block } => number of items
@return [Integer] the number of leading elements for which block is false.
# File lib/sixarm_ruby_ramp/enumerable/nitems.rb, line 38 def nitems_until num = 0 each{|item| if yield(item) break else num+=1 end } return num end
@example
enum.nitems_while {| obj | block } => number of items
@return [Integer] the number of leading elements for which block is not false or nil.
# File lib/sixarm_ruby_ramp/enumerable/nitems.rb, line 26 def nitems_while num = 0 each{|item| yield(item) ? (num+=1) : break} return num end
Calls block with two arguments, the item and its index, for each item in enum.
@example
enum.nitems_with_index {|obj,i| block } => number of items
@return [Integer] the number of leading elements for which block is true.
# File lib/sixarm_ruby_ramp/enumerable/nitems.rb, line 59 def nitems_with_index index = 0 each{|item| yield(item,index) ? (index+=1) : break} return index end
Calculate the power set.
@return [Array<Array<Object>>] the power set: an array with all subsets of the enum's elements. @see en.wikipedia.org/wiki/Power_set
This implementation is from the blog post below: @see johncarrino.net/blog/2006/08/11/powerset-in-ruby/
@example
[1,2,3].power_set.sort => [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
# File lib/sixarm_ruby_ramp/enumerable.rb, line 230 def power_set inject([[]]){|c,y|r=[];c.each{|i|r<<i;r<<i+[y]};r} end
@example
enum.select_until {|obj| block } => array
@return [Array<Object>] the leading elements for which block is falsey.
# File lib/sixarm_ruby_ramp/enumerable/select.rb, line 21 def select_until arr = [] each{|item| yield(item) ? break : (arr << item)} return arr end
@example
enum.select_while {|obj| block } => array
@return [Array<Object>] the leading elements for which block is truthy.
# File lib/sixarm_ruby_ramp/enumerable/select.rb, line 9 def select_while arr = [] each{|item| yield(item) ? (arr << item) : break} return arr end
Calls block with two arguments, the item and its index, for each item in enum.
@example
enum.select_with_index {|obj,i| block } => array
@return [Array<Object> the leading elements for which block is truthy.
# File lib/sixarm_ruby_ramp/enumerable/select.rb, line 35 def select_with_index index = 0 arr = [] each{|item| if yield(item,index) arr << item index+=1 else break end } return arr end
# File lib/sixarm_ruby_ramp/enumerable.rb, line 21 def to_h hash={} each{|key,val| hash[key]=val } return hash end
Convert an enumerable to a hash and merge values.
@return [Hash<Object,Object>] a hash of the enumerable's items
@example
array=[[:a, :b],[:c, :d],[:e, :f]] array.to_h => {:a=>:b, :c=>:d, :e=>:f}
If a key occurs more than once, then this will automatically merge the values to an array of the keys' values.
@example
array=[[:a,:b],[:a,:c],[:a,:d]] array.to_h => {:a=>[:b, :c, :d]}
# File lib/sixarm_ruby_ramp/enumerable.rb, line 45 def to_h_merge hash={} seen=Set.new each{|key,val| if hash.key? key if seen.include? key hash[key] << val else hash[key]=[hash[key]] hash[key] << val seen << key end else hash[key]=val end } return hash end