module Enumerable
This is a simple reimplementation of the core Enumerable module to allow the methods to take and pass-on arbitrary arguments to the underlying each call. This library uses Enumerator and scans Enumerable so it can alwasy stay in sync.
NOTE Any Enumerable method with a negative arity cannot do pass arguments due to ambiguity in the argument count. So the methods inject and zip do NOT work this way, but simply work as they do in Enumerable. The method find (and detect) though has been made to work by removing its rarely used optional parameter and providing instead an optional keyword parameter (:ifnone => …). Please keep these difference in mind.
require 'enumargs' class T include Enumerable::Arguments def initialize(arr) @arr = arr end def each(n) arr.each{ |e| yield(e+n) } end end t = T.new([1,2,3]) t.collect(4) #=> [5,6,7]
Public Instance Methods
Accumulate a set of a set.
For example, in an ORM design if Group has_many User then
groups.accumulate.users
will return a list of users from all groups.
CREDIT: George Moshchovitis
# File lib/core/facets/enumerable/accumulate.rb, line 16 def accumulate @_accumulate ||= Functor.new do |op, *args| inject([]) { |a, x| a << x.send(op, *args) }.flatten 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]}
produces
[ ['a'], ['is'], ['this', 'test'] ]
CREDIT: Erik Veenstra
# File lib/core/facets/enumerable/cluster_by.rb, line 16 def cluster_by(&b) group_by(&b).sort.transpose.pop || [] # group_by(&b).values ? end
Returns all items that are equal in terms of the supplied block. If no block is given objects are considered to be equal if they return the same value for Object#hash and if obj1 == obj2.
[1, 2, 2, 3, 4, 4].commonality # => { 2 => [2], 4 => [4] } ["foo", "bar", "a"].commonality { |str| str.length } # => { 3 => ["foo, "bar"] } # Returns all persons that share their last name with another person. persons.collisions { |person| person.last_name }
CREDIT: Florian Gross
# File lib/core/facets/enumerable/commonality.rb, line 17 def commonality( &block ) had_no_block = !block block ||= lambda { |item| item } result = Hash.new { |hash, key| hash[key] = Array.new } self.each do |item| key = block.call(item) result[key] << item end result.reject! do |key, values| values.size <= 1 end #return had_no_block ? result.values.flatten : result return result end
A more versitle compact method. It can be used to collect and filter items out in one single step.
[1,2,3].compact_map do |n| n < 1 ? nil : n end
produces
[2,3]
NOTE: Perhaps nicer to have as added functionality for compact.
CREDIT: Trans
# File lib/core/facets/enumerable/compact_map.rb, line 18 def compact_map(trash=nil, &block) y = [] if block_given? each do |*a| r = yield(*a) y << r unless trash == r end else each do |r| y << r unless trash == r 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 e.count('a', 1) #=> 3
Note that Hash#count only considers values.
e = { 'a' => 2, 'x' => 2, 'b' => 1 } e.count(1) #=> 1 e.count(2) #=> 2
CREDIT: Trans
# File lib/core/facets/enumerable/count.rb, line 23 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). each { |i| puts i }
Using with a block, defer(&b), is equivalent to:
defer.filter(&b)
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/core/facets/enumerable/defer.rb, line 34 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
Divide on matching pattern.
['a1','b1','a2','b2'].divide(/^a/) => [['a1,'b1'],['a2','b2']]
CREDIT: Trans
# File lib/core/facets/enumerable/divide.rb, line 10 def divide(pattern) memo = [] each do |obj| memo.push [] if pattern === obj memo.last << obj end memo 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/core/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
Returns an elementwise Functor designed to make R-like elementwise operations possible.
[1,2].elementwise + 3 #=> [4,5] [1,2].elementwise + [4,5] #=> [5,7] [1,2].elementwise + [[4,5],3] #=> [[5,7],[4,5]
# File lib/core/facets/enumerable/ewise.rb, line 16 def elementwise(count=1) @_elementwise_functor ||= [] @_elementwise_functor[count] ||= 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).zip(arg[0...ln]).collect{ |a,b| a.send(op,b) } #slice(0...ln).zip(arg[0...1n]).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
Shannon's entropy for an array - returns the average bits per symbol required to encode the array. Lower values mean less “entropy” - i.e. less unique information in the array.
%w{ a b c d e e e }.entropy #=>
CREDIT: Derek
# File lib/core/facets/enumerable/entropy.rb, line 14 def entropy arr = to_a probHash = arr.probability # h is the Shannon entropy of the array h = -1.to_f * probHash.keys.inject(0.to_f) do |sum, i| sum + (probHash[i] * (Math.log(probHash[i])/Math.log(2.to_f))) end h end
Returns an elemental object. This allows you to map a method on to every element.
r = [1,2,3].every + 3 #=> [4,5,6]
# File lib/core/facets/enumerable/every.rb, line 10 def every @_every ||= per(:map) end
In place version of every.
# File lib/core/facets/enumerable/every.rb, line 16 def every! raise NoMethodError unless respond_to?(:map!) @_every_inplace ||= per(:map!) end
Concise alias for elementwise.
a = [1,2] a.ewise + 3 #=> [4,5] a.ewise + [4,5] #=> [5,7] a.ewise + [[4,5],3] #=> [[5,7],[4,5]
Note this used to be ew as weel as the '%' operator. Both of whihc are deprecated.
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). filter { |out,i| out << i if i % 2 == 0 }. # like select filter { |out,i| out << i + 100 }. # like map take(10).each { |i| puts i }
# File lib/core/facets/enumerable/filter.rb, line 13 def filter(output=[]) if block_given? each do |*input| yield(output, *input) end output else to_enum(:filter) 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] } ["I had", 1, "dollar and", 50, "cents"].group_by { |e| e.class } #=> { String => ["I had","dollar and","cents"], Fixnum => [1,50] }
CREDIT: Erik Veenstra
# File lib/core/facets/enumerable/group_by.rb, line 17 def group_by #:yield: #h = k = e = nil r = Hash.new each{ |e| (r[yield(e)] ||= []) << e } r end
Returns the maximum possible Shannon entropy of the array with given size assuming that it is an “order-0” source (each element is selected independently of the next).
CREDIT: Derek
# File lib/core/facets/enumerable/entropy.rb, line 30 def ideal_entropy arr = to_a unitProb = 1.0.to_f / arr.size.to_f (-1.to_f * arr.size.to_f * unitProb * Math.log(unitProb)/Math.log(2.to_f)) end
A small variation of #Inject that save one from having to return the aggregating or memo argument.
Say you want to count letters.
some_text.inject!(Hash.new(0)) {|h,l| h[l] += 1}
vs
some_text.inject(Hash.new(0)) {|h,l| h[l] +=1; h}
CREDIT: David Black, Louis J Scoras
# File lib/core/facets/enumerable/inject.rb, line 16 def inject!(s) k = s each { |i| yield(k, i) } k end
Yield each element to the block. Returns the result of the block when the block is true, terminating early as detect does.
obj1.foo? #=> false obj2.foo? #=> true obj2.foo #=> "a value" [obj1, obj2].map_detect { |obj| obj.foo if obj.foo? } #=> "a value"
If the block is never true, return the object given in the first parameter, or nil if none specified.
[1,2,3].map_detect { |_| false } #=> nil [false].map_detect(1) { |_| false } #=> 1
# File lib/core/facets/enumerable/map_detect.rb, line 19 def map_detect(value_for_none_matching = nil) each do |member| if result = yield(member) return result end end value_for_none_matching end
Send a message to each element and collect the result.
CREDIT: Sean O'Halpin
# File lib/core/facets/enumerable/map_send.rb, line 7 def map_send(meth, *args, &block) map{|e| e.send(meth, *args, &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/core/facets/enumerable/map_with_index.rb, line 10 def map_with_index r = [] each_index do |i| r << yield(self[i], i) end r 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.mash { |n| [n, n*n] } # { 1=>1, 2=>4, 3=>9 } sq_roots = numbers.mash { |n| [n*n, n] } # { 1=>1, 4=>2, 9=>3 }
The name “mash” stands for “map hash”.
CREDIT: Andrew Dudzik (adudzik), Trans
# File lib/core/facets/enumerable/mash.rb, line 14 def mash(&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,:mash) end end
In Statistics mode is the value that occurs most frequently in a given set of data.
CREDIT: Robert Klemme
# File lib/core/facets/enumerable/mode.rb, line 8 def mode max = 0 c = Hash.new 0 each {|x| cc = c[x] += 1; max = cc if cc > max} c.select {|k,v| v == max}.map {|k,v| k} end
Modulate. Divide an array into groups by modulo of the index.
- 2,4,6,8].modulate(2) #=> [[2,6],
-
CREDIT: Trans
NOTE: Would the better name for this be 'collate'?
# File lib/core/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/core/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.
[1,1,2,3,3,4,5,5].occur(1) #=> [2,4] [1,1,2,3,3,4,5,5].occur(2) #=> [1,3,5] [1,1,2,3,3,4,5,5].occur(3) #=> [] [1,2,2,3,3,3].occur(1..1) #=> [1] [1,2,2,3,3,3].occur(2..3) #=> [2,3] [1,1,2,3,3,4,5,5].occur { |n| n == 1 } #=> [2,4] [1,1,2,3,3,4,5,5].occur { |n| n > 1 } #=> [1,3,5]
# File lib/core/facets/enumerable/occur.rb, line 16 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/core/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 + 3 #=> [4,5,6] [1,2,3].per(:map) + 3 #=> [4,5,6] [1,2,3].per(:select) > 1 #=> [2,3] [1,2,3].per.map + 3 #=> [4,5,6] [1,2,3].per.select > 1 #=> [2,3]
# File lib/core/facets/enumerable/per.rb, line 18 def per(enum_method=nil, *enum_args) if enum_method Permeator.new(self, enum_method, *enum_args) #Functor.new do |op, *args| # __send__(enum_method, *enum_args){ |x| x.__send__(op, *args) } #, &blk) } #end else @__per__ ||= Functor.new do |enum_method, *enum_args| Permeator.new(self, enum_method, *enum_args) #Functor.new do |op, *args| # __send__(enum_method, *enum_args){ |x| x.__send__(op, *args) } #, &blk) } #end end end end
Split on matching pattern. Unlike divide this does not include matching elements.
['a1','a2','b1','a3','b2','a4'].split(/^b/) => [['a1','a2'],['a3'],['a4']]
CREDIT: Trans
# File lib/core/facets/enumerable/split.rb, line 10 def split(pattern) memo = [] sect = [] each do |obj| if pattern === obj memo << sect sect = [] else sect << obj end end memo << sect memo.pop while memo.last == [] memo end
Uses #+ to sum the enumerated elements.
[1,2,3].sum #=> 6 [3,3,3].sum #=> 9
# File lib/core/facets/enumerable/sum.rb, line 8 def sum(identity = 0, &block) if block_given? map(&block).sum else inject{ |sum, element| sum + element } || identity end end
Return the first n items from the collection
# File lib/core/facets/enumerable/take.rb, line 7 def take(n) res = [] count = 0 each do |e| break if count >= n res << e count += 1 end res end
Like Enumerable#map but each iteration is processed via a separate thread.
CREDIT Sean O'Halpin
# File lib/more/facets/thread.rb, line 37 def threaded_map #:yield: map{ |e| Thread.new(e){ |t| yield(t) } }.map{ |t| t.value } end
Like #map_send but each iteration is processed via a separate thread.
CREDIT Sean O'Halpin
# File lib/more/facets/thread.rb, line 46 def threaded_map_send(meth, *args, &block) map{ |e| Thread.new(e){ |t| t.send(meth, *args, &block) } }.map{ |t| t.value } end
Convert an Enumerable object into a hash by first turning it into an array.
CREDIT: Trans
# File lib/core/facets/to_hash.rb, line 225 def to_h(mode=nil) to_a.to_h(mode) end
# File lib/core/facets/to_hash.rb, line 241 def to_h_assoc to_a.to_h_assoc end
# File lib/core/facets/to_hash.rb, line 229 def to_h_auto to_a.to_h_auto end
# File lib/core/facets/to_hash.rb, line 237 def to_h_flat to_a.to_h_flat end
# File lib/core/facets/to_hash.rb, line 245 def to_h_multi to_a.to_h_multi end
# File lib/core/facets/to_hash.rb, line 233 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 }
produces
[-5, -4, -3, -2, -1, 0]
# File lib/core/facets/enumerable/uniq_by.rb, line 11 def uniq_by #:yield: h = {}; inject([]) {|a,x| h[yield(x)] ||= a << x} end