module Enumerable
Public Instance Methods
Accumulate a set of a set. For example, in an ORM design where `Group has_many User` we might have something equivalent to the following.
Group = Struct.new(:users) User = Struct.new(:name, :friends) user1 = User.new('John', []) user2 = User.new('Jane', ['Jill']) user3 = User.new('Joe' , ['Jack', 'Jim']) group1 = Group.new([user1, user2]) group2 = Group.new([user2, user3]) groups = [group1, group2]
Now we can accumulate the users of all groups.
groups.accumulate.users #=> [user1, user2, user3]
You may pass an argument to perform chains, e.g. the following returns the names of users from all groups.
groups.accumulate(2).users.name #=> ['John','Jane','Joe']
Or we can gather all the friends of all users in groups.
groups.accumulate(2).users.friends #=> ['Jill','Jack','Jim']
This is more convenient then the equivalent.
groups.accumulate.users.accumulate.friends #=> ['Jill','Jack','Jim']
CREDIT: George Moshchovitis, Daniel Emirikol
# File lib/facets/enumerable/accumulate.rb, line 40 def accumulate(iterations=1) return self if iterations == 0 Functor.new do |op, *args| #result = inject([]) { |a, x| a << x.send(op, *args) }.flatten.uniq result = [] each { |x| result << x.send(op, *args) } result.flatten.uniq.accumulate(iterations - 1) end end
Same as accumulate, but does not apply uniq to final result.
groups.accumulate_all(2).users.friends #=> ['Jill', 'Jill','Jack','Jim']
# File lib/facets/enumerable/accumulate.rb, line 54 def accumulate_all(iterations=1) return self if iterations == 0 Functor.new do |op, *args| #result = inject([]) { |a, x| a << x.send(op, *args) }.flatten result = [] each { |x| result << x.send(op, *args) } result.flatten.accumulate_all(iterations - 1) end end
Similar to group_by but returns an array of the groups. Returned elements are sorted by block.
%w{this is a test}.cluster_by {|x| x[0]} #=> [ ['a'], ['is'], ['this', 'test'] ]
CREDIT: Erik Veenstra
# File lib/facets/enumerable/cluster_by.rb, line 13 def cluster_by(&b) group_by(&b).sort.transpose.pop || [] # group_by(&b).values ? end
A more versitle compact method. It can be used to collect and filter items out in one single step.
c = [1,2,3].compact_map do |n| n < 2 ? nil : n end c #=> [2,3]
CREDIT: Trans
DEPRECATE: This method should probably be removed b/c purge does almost the same thing and enum.map{}.compact works too.
# File lib/facets/enumerable/compact_map.rb, line 17 def compact_map(&block) y = [] if block_given? each do |*a| r = yield(*a) y << r unless r.nil? end else each do |r| y << r unless r.nil? end end y end
Count the number of items in an enumerable equal (==) to the given object(s).
e = [ 'a', 1, 'a' ] e.count(1) #=> 1 e.count('a') #=> 2
The method can count more than one item by supplying additional arguments.
e.count('a', 1) #=> 3
A block may also be used to select countable entries.
e.count{ |x| String === x } #=> 2
Note that Hash#count only considers values.
e = { 'a' => 2, 'x' => 2, 'b' => 1 } e.count(1) #=> 1 e.count(2) #=> 2
NOTE: This is one of the few method overrides in Facets.
CREDIT: Trans
# File lib/facets/enumerable/count.rb, line 33 def count(*items, &block) if block || !items.empty? r = self r = r.select(&block) if block r = r.select{ |x| items.any?{ |i| i == x } } if !items.empty? r.size else begin size rescue i=0; each{ |e| i+=1 }; i end end end
Without a block: wrap the Enumerable object in such a way that map, select and similar operations are performed “horizontally” across a series of blocks, instead of building an array of results at each step. This reduces memory usage, allows partial results to be provided early, and permits working with infinite series.
a = (1..1_000_000_000).defer.select{ |i| i % 2 == 0 }. map{ |i| i + 100 }. take(10).to_a
With a block: the block acts as an arbitrary filter on the data. Unlike map, it can choose to drop elements from the result, and/or add additional ones. The first object passed to the block is the receiver of the output.
(1..1_000_000_000). defer { |out,i| out << i if i % 2 == 0 }. # like select defer { |out,i| out << i + 100 }. # like map take(10).to_a
Use a method like to_a or #to_h at the end of the chain when you want an Array or Hash built with the results, or each{…} if you just want to output each result and discard it.
# File lib/facets/enumerable/defer.rb, line 29 def defer(&blk) if block_given? Denumerator.new do |output| each do |*input| yield(output, *input) end end else Denumerator.new do |output| each do |*input| output.yield(*input) end end end end
Iterate through slices. If slice steps
is not given, the arity
of the block is used.
x = [] [1,2,3,4].each_by{ |a,b| x << [a,b] } x #=> [ [1,2], [3,4] ] x = [] [1,2,3,4,5,6].each_by(3){ |a| x << a } x #=> [ [1,2,3], [4,5,6] ]
This is just like each_slice, except that it will check the arity of the block. If each_slice ever suppots this this method can be deprecated.
CREDIT: Trans
# File lib/facets/enumerable/each_by.rb, line 22 def each_by(steps=nil, &block) if steps each_slice(steps, &block) else steps = block.arity.abs each_slice(steps, &block) #each_slice(steps) {|i| block.call(*i)} end end
A variation of inject that saves one from having to return the aggregate/memo argument.
Say we want to count characters in a string. Using the each_with_object method we have:
"string".each_with_object(Hash.new(0)) do |c, h| h[c] += 1 end
versus using inject which would be:
"string".inject(Hash.new(0)) do |h, c| h[c] +=1 h end
Notice that the order of the block parameters is reversed.
This method used be called injecting and had the same parameter order as inject, but Ruby 1.9 has adopted this method, so we support it instead.
# File lib/facets/enumerable/each_with_object.rb, line 28 def each_with_object(memo) #:yield: each do |element| yield(element, memo) end memo end
Long-term for ewise.
a = [1,2] (a.elementwise + 3) #=> [4,5] (a.elementwise + [4,5]) #=> [5,7]
Returns an elemental object. This allows you to map a method on to every element.
r = [1,2,3].every + 3 r #=> [4,5,6]
# File lib/facets/enumerable/every.rb, line 11 def every per(:map) end
In place version of every.
# File lib/facets/enumerable/every.rb, line 17 def every! raise NoMethodError unless respond_to?(:map!) per(:map!) end
Returns an elementwise Functor designed to make R-like elementwise operations possible. This is very much like the every method, but it treats array argument specially.
([1,2].ewise + 3) #=> [4,5]
Vector to vector
([1,2].ewise + [4,5]) #=> [5,7]
Special thanks to Martin DeMello for helping to develop this.
# File lib/facets/enumerable/ewise.rb, line 17 def ewise(count=1) Functor.new do |op,*args| if args.empty? r = self count.times do r = r.collect{ |a| a.send(op) } end r else r = args.collect do |arg| if Array === arg #arg.kind_of?(Enumerable) x = self count.times do ln = (arg.length > length ? length : arg.length ) x = x.slice(0...ln) x = x.zip(arg[0...ln]) x = x.collect{ |a,b| a.send(op,b) } #x = x.collect{ |a,b| b ? a.send(op,b) : nil } end x else x = self count.times do x = x.collect{ |a| a.send(op,arg) } end x end end r.flatten! if args.length == 1 r end end end
The inverse of include?.
[:a, :b].exclude?(:c) #=> true [:a, :b].exclude?(:a) #=> false
# File lib/facets/enumerable/exclude.rb, line 10 def exclude?(object) !include?(object) end
Expand all elements of an Enumerable object.
[0, 2..3, 5..7].expand #=> [0,[2, 3],[5,6,7]]
CREDIT: Trans
# File lib/facets/enumerable/expand.rb, line 8 def expand map do |x| (Enumerable === x ? x.expand : x) end end
The block acts as an arbitrary filter on the data. Unlike map, it can choose to drop elements from the result and/or add additional elements. The first object passed to the block is the receiver of the output.
x = (1..10000) x = x.filter{ |out,i| out << i if i % 2 == 0 } # like select x = x.filter{ |out,i| out << i + 100 } # like map x = x.take(3) x #=> [102, 104, 106]
This is very similar to each_with_object, but filter handles argument better by reversing their order and using the splat operator. (This was also once known as injecting.)
CREDIT: David Black, Louis J Scoras
# File lib/facets/enumerable/filter.rb, line 21 def filter(output=[]) #:yeild: if block_given? each do |*input| yield(output, *input) end output else to_enum(:filter) end end
Yield each element to the block and return the result of the block when that result evaluates as true, terminating early like detect and find.
obj1 = Object.new obj2 = Object.new def obj1.foo?; false; end def obj2.foo?; true ; end def obj1.foo ; "foo1"; end def obj2.foo ; "foo2"; end [obj1, obj2].find_yield{ |obj| obj.foo if obj.foo? } #=> "foo2"
Another example.
[1,2,3,4,5].find_yield{ |i| j = i+1; j if j % 4 == 0 } #=> 4
If the block is never true, return the object given in the first parameter, or nil if none specified.
[1,2,3].find_yield{ |_| false } #=> nil [false].find_yield(1){ |_| false } #=> 1
# File lib/facets/enumerable/find_yield.rb, line 28 def find_yield(fallback=nil) #:yield: each do |member| result = yield(member) return result if result end fallback end
Generates a hash mapping each unique symbol in the array to the absolute frequency it appears.
[:a,:a,:b,:c,:c,:c].frequency #=> {:a=>2,:b=>1,:c=>3}
CREDIT: Brian Schröder
# File lib/facets/enumerable/frequency.rb, line 18 def frequency p = Hash.new(0); each{ |v| p[v] += 1 }; p end
Like `#map`/`#collect`, but generates a Hash. The block is expected to return two values: the key and the value for the new hash.
numbers = (1..3) squares = numbers.graph{ |n| [n, n*n] } # { 1=>1, 2=>4, 3=>9 } sq_roots = numbers.graph{ |n| [n*n, n] } # { 1=>1, 4=>2, 9=>3 }
CREDIT: Andrew Dudzik (adudzik), Trans
# File lib/facets/enumerable/graph.rb, line 12 def graph(&yld) if yld h = {} each do |*kv| r = yld[*kv] case r when Hash nk, nv = *r.to_a[0] when Range nk, nv = r.first, r.last else nk, nv = *r end h[nk] = nv end h else Enumerator.new(self,:graph) end end
#group_by is used to group items in a collection by something they have in common. The common factor is the key in the resulting hash, the array of like elements is the value.
(1..5).group_by { |n| n % 3 } #=> { 0 => [3], 1 => [1, 4], 2 => [2,5] }
Applied to an array.
["I had", 1, "dollar and", 50, "cents"].group_by { |e| e.class } #=> { String => ["I had","dollar and","cents"], Fixnum => [1,50] }
Applied to a hash:
{:a=>1, :b=>2, :c=>1}.group_by{ |k,v| v } #=> { 1=>[[:c,1], [:a,1]], 2=>[[:b,2]] }
CREDIT: Erik Veenstra
# File lib/facets/enumerable/group_by.rb, line 24 def group_by #:yield: r = Hash.new each{ |e| (r[yield(e)] ||= []) << e } r end
Create a hash whose keys are the enumerable's elements, with specified values.
If no block is given, the given parameter (default true) is used for all values, e.g.:
[1,2,3,4].hashify #=> {1=>true, 2=>true, 3=>true, 4=>true} [1,2,3,4].hashify("a") #=> {1=>"a", 2=>"a", 3=>"a", 4=>"a"}
If a block is given, each key's value is the result of running the block for that key, e.g.:
[1,2,3,4].hashify{|n| "a" * n} #=> {1=>"a", 2=>"aa", 3=>"aaa", 4=>"aaaa"}
@author Ronen Barzel
@uncommon
require 'facets/enumerable/hashify'
# File lib/facets/enumerable/hashify.rb, line 21 def hashify(val=true) h = {} if block_given? each { |item| h[item] = yield(item) } else each { |item| h[item] = val } end h end
The same as include? but tested using === instead of #==.
[1, 2, "a"].incase?(2) #=> true [1, 2, "a"].incase?(String) #=> true [1, 2, "a"].incase?(3) #=> false
Why the name `incase`? Because the method uses case-equality. Along with the alliteration for “in case” and the similarity with “include?”, it seemed like the perfect fit.
@author Lavir the Whiolet
# File lib/facets/enumerable/incase.rb, line 15 def incase?(what) any? { |x| what === x } end
Like group_by, but maps the second value returned from the block.
a = [1,2,3,4,5] a.map_by{ |e| [e % 2, e + 1] } #=> { 0=>[3,5], 1=>[2,4,6] }
Works well with a hash too.
h = {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2} h.map_by{ |k,v| [v, k.downcase] } #=> {1=>["a", "b", "c"], 2=>["d", "e"]}
If a second value is not returned, map_by acts like group_by.
h = {"A"=>1, "B"=>1, "C"=>1, "D"=>2, "E"=>2} h.map_by{ |k,v| v } #=> {1=>[["A",1], ["B",1], ["C",1]], 2=>[["D",2], ["E",2]]}
# File lib/facets/enumerable/map_by.rb, line 21 def map_by #:yield: res = {} each do |a| k,v = yield(*a) if v (res[k] ||= []) << v else (res[k] ||= []) << a end end res end
Alias for find_yield.
DEPRECATE: This has been renamed to find_yield.
Send a message to each element and collect the result.
[1,2,3].map_send(:+, 3) #=> [4,5,6]
CREDIT: Sean O'Halpin
# File lib/facets/enumerable/map_send.rb, line 9 def map_send(meth, *args, &block) map{|e| e.send(meth, *args, &block)} end
Combines zip and map in a single efficient operation.
h = {} [1,2,3].map_with [:x,:y,:z] do |n,k| h[k] = n end h #=> {:x=>1, :y=>2, :z=>3}
@return [Hash]
@author Michael Kohl
# File lib/facets/enumerable/map_with.rb, line 14 def map_with(*arrays, &block) enum_for(:zip, *arrays).map(&block) end
Same as collect but with an iteration counter.
a = [1,2,3].collect_with_index { |e,i| e*i } a #=> [0,2,6]
CREDIT: Gavin Sinclair
# File lib/facets/enumerable/map_with_index.rb, line 10 def map_with_index r = [] each_with_index do |e, i| r << yield(e, i) end r end
Divide an array into groups by modulo of the index.
[2,4,6,8].modulate(2) #=> [[2,6],[4,8]]
CREDIT: Trans
NOTE: Would the better name for this be 'collate'?
# File lib/facets/enumerable/modulate.rb, line 11 def modulate(modulo) return to_a if modulo == 1 raise ArgumentError, 'bad modulo' if size % modulo != 0 r = Array.new(modulo, []) (0...size).each do |i| r[i % modulo] += [self[i]] end r end
#none? is the logical
opposite of the builtin method Enumerable#any?. It returns
true
if and only if none of the elements in the
collection satisfy the predicate.
If no predicate is provided, #none? returns
true
if and only if none of the elements have a true
value (i.e. not nil
or false
).
[].none? # => true [nil].none? # => true [5,8,9].none? # => false (1...10).none? { |n| n < 0 } # => true (1...10).none? { |n| n > 0 } # => false
CREDIT: Gavin Sinclair
# File lib/facets/enumerable/none.rb, line 21 def none? # :yield: e if block_given? not self.any? { |e| yield e } else not self.any? end end
Returns an array of elements for the elements that occur n times. Or according to the results of a given block.
a = [1,1,2,3,3,4,5,5] a.occur(1).sort #=> [2,4] a.occur(2).sort #=> [1,3,5] a.occur(3).sort #=> [] a.occur(1..1).sort #=> [2,4] a.occur(2..3).sort #=> [1,3,5] a.occur { |n| n == 1 }.sort #=> [2,4] a.occur { |n| n > 1 }.sort #=> [1,3,5]
# File lib/facets/enumerable/occur.rb, line 18 def occur(n=nil) #:yield: result = Hash.new { |hash, key| hash[key] = Array.new } self.each do |item| key = item result[key] << item end if block_given? result.reject! { |key, values| ! yield(values.size) } else raise ArgumentError unless n if Range === n result.reject! { |key, values| ! n.include?(values.size) } else result.reject! { |key, values| values.size != n } end end return result.values.flatten.uniq end
#one? returns
true
if and only if exactly one element in the
collection satisfies the given predicate.
If no predicate is provided, #one? returns true
if and only if exactly one element has a true value (i.e. not
nil
or false
).
[].one? # => false [nil].one? # => false [5].one? # => true [5,8,9].one? # => false (1...10).one? { |n| n == 5 } # => true (1...10).one? { |n| n < 5 } # => false
CREDIT: Gavin Sinclair
# File lib/facets/enumerable/one.rb, line 21 def one? # :yield: e matches = 0 if block_given? self.each do |e| if yield(e) matches += 1 return false if matches > 1 end end return (matches == 1) else one? { |e| e } end end
Per element meta-functor.
([1,2,3].per(:map) + 3) #=> [4,5,6] ([1,2,3].per(:select) > 1) #=> [2,3]
Using fluid notation.
([1,2,3].per.map + 3) #=> [4,5,6] ([1,2,3].per.select > 1) #=> [2,3]
# File lib/facets/enumerable/per.rb, line 19 def per(enum_method=nil, *enum_args) if enum_method Permeator.new(self, enum_method, *enum_args) else Functor.new do |enumr_method, *enumr_args| Permeator.new(self, enumr_method, *enumr_args) end end end
A versitle compaction method. Like map but used to filter out multiple items in a single step.
Without trash
arguments nil
is assumed.
[1, nil, 2].purge #=> [1,2]
If trash
arguments are given, each argument is compared for a
match using #==.
(1..6).purge(3,4) #=> [1,2,5,6]
If a block is given, the yield is used in the matching condition instead of the element itsef.
(1..6).purge(0){ |n| n % 2 } #=> [1,3,5]
NOTE: This could just as well be an override of the core compact method, but to avoid potential issues associated with overriding core methods we use the alternate name purge.
CREDIT: Trans
# File lib/facets/enumerable/purge.rb, line 27 def purge(*trash, &block) trash = [nil] if trash.empty? r = [] if block_given? each do |e| y = yield(e) r << e unless trash.any?{|t| t == y} end else each do |e| r << e unless trash.any?{|t| t == e} end end r end
Returns a recursive functor, that allows enumerable methods to iterate through enumerable sub-elements. By default it only recurses over elements of the same type.
# File lib/facets/enumerable/recursively.rb, line 7 def recursively(*types, &block) Recursor.new(self, *types, &block) end
Uses #+ to sum the enumerated elements.
[1,2,3].sum #=> 6 [3,3,3].sum #=> 9
@author Dawid Marcin Grzesiak (bug fix)
# File lib/facets/enumerable/sum.rb, line 10 def sum(identity=0, &block) if block_given? map(&block).sum(identity) else inject{ |sum, element| sum + element } || identity end end
Return the first n items from the collection
# File lib/facets/enumerable/take.rb, line 5 def take(n) res = [] count = 0 each do |e| break if count >= n res << e count += 1 end res end
Convert an Enumerable object into a hash by first turning it into an array.
CREDIT: Trans
# File lib/facets/to_hash.rb, line 227 def to_h(mode=nil) to_a.to_h(mode) end
# File lib/facets/to_hash.rb, line 243 def to_h_assoc to_a.to_h_assoc end
# File lib/facets/to_hash.rb, line 231 def to_h_auto to_a.to_h_auto end
# File lib/facets/to_hash.rb, line 239 def to_h_flat to_a.to_h_flat end
# File lib/facets/to_hash.rb, line 247 def to_h_multi to_a.to_h_multi end
# File lib/facets/to_hash.rb, line 235 def to_h_splat to_a.to_h_splat end
Like uniq, but determines uniqueness based on a given block.
(-5..5).to_a.uniq_by {|i| i*i } #=> [-5, -4, -3, -2, -1, 0]
# File lib/facets/enumerable/uniq_by.rb, line 8 def uniq_by #:yield: h = {}; inject([]) {|a,x| h[yield(x)] ||= a << x} end
Recursively iterate over all Enumerable elements, or subset given :type=>[type1, type2, …].
[1, 2, 8..9].visit{ |x| x.succ } # => [2, 3, [9, 10]]
# File lib/facets/enumerable/visit.rb, line 9 def visit(opts={}, &block) type = opts[:type] ? [opts[:type]].flatten : [Enumerable] skip = opts[:skip] map do |v| case v when String # b/c of 1.8 block.call(v) when *type v.visit(opts, &block) else if skip && Enumerable === v v else block.call(v) end end end end