Module: Enumerable

Defined in:
lib/nano/enumerable/one%3F.rb,
lib/nano/enumerable/eF.rb,
lib/nano/enumerable/%25.rb,
lib/nano/enumerable/to_h.rb,
lib/nano/enumerable/cross.rb,
lib/nano/enumerable/graph.rb,
lib/nano/enumerable/occur.rb,
lib/nano/enumerable/%2A%2A.rb,
lib/nano/enumerable/each_by.rb,
lib/nano/enumerable/entropy.rb,
lib/nano/enumerable/none%3F.rb,
lib/nano/enumerable/frequency.rb,
lib/nano/enumerable/unique_by.rb,
lib/nano/enumerable/each_slice.rb,
lib/nano/enumerable/filter_map.rb,
lib/nano/enumerable/%3A%3Across.rb,
lib/nano/enumerable/commonality.rb,
lib/nano/enumerable/compact_map.rb,
lib/nano/enumerable/probability.rb,
lib/nano/enumerable/partition_by.rb,
lib/nano/enumerable/ideal_entropy.rb,
lib/nano/enumerable/filter_collect.rb,
lib/nano/enumerable/map_with_index.rb,
lib/nano/enumerable/compact_collect.rb,
lib/nano/enumerable/find_collisions.rb,
lib/nano/enumerable/map_with_counter.rb,
lib/nano/enumerable/%3A%3Acombinations.rb,
lib/nano/enumerable/collect_with_index.rb,
lib/nano/enumerable/collect_with_counter.rb

Overview

– Credit goes to Florian Gross. ++

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.combinations(head, *rest) ⇒ Object

Produces an array of arrays of all possible combinations of the given arrays in the position given. (Explain me better?)

a = %w|a b|
b = %w|a x|
c = %w|x y|
Array.combinations(a, b, c).each { |x| p x }

produces

["a", "a", "x"]
["a", "a", "y"]
["a", "x", "x"]
["a", "x", "y"]
["b", "a", "x"]
["b", "a", "y"]
["b", "x", "x"]
["b", "x", "y"]


23
24
25
26
27
28
# File 'lib/nano/enumerable/%3A%3Acombinations.rb', line 23

def self.combinations(head, *rest)
  crest = rest.empty? ? [[]] : combinations(*rest)
  head.inject([]) { |combs, item|
    combs + crest.map { |comb| [item] + comb }
  }
end

.cross(*enums, &block) ⇒ Object

Provides the cross-product of two or more Enumerables.

This is the class-level method. The instance method
calls on this.

  Enumerable.cross([1,2], [4], ["apple", "banana"])
  #=> [[1, 4, "apple"], [1, 4, "banana"], [2, 4, "apple"], [2, 4, "banana"]]

  Enumerable.cross([1,2], [3,4])
  #=> [[1, 3], [1, 4], [2, 3], [2, 4]]


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/nano/enumerable/%3A%3Across.rb', line 18

def self.cross(*enums, &block)
  raise if enums.empty?
  gens = enums.map{|e| Generator.new(e)}
  return [] if gens.any? {|g| g.end?}
  sz = gens.size
  res = []
  tuple = Array.new(sz)

  loop do
    # fill tuple
    (0 ... sz).each { |i| 
      tuple[i] = gens[i].current 
    }
    if block.nil?
      res << tuple.dup
    else
      block.call(tuple.dup)
    end

    # step forward
    gens[-1].next
    (sz-1).downto(0) { |i|
      if gens[i].end?
        if i > 0
          gens[i].rewind
          gens[i-1].next
        else
          return res
        end
      end
    }
  end
end

Instance Method Details

#**(enum) ⇒ Object

Operator alias for cross-product.

a = [1,2] ** [4,5]
a  #=> [[1, 4],[1, 5],[2, 4],[2, 5]]


11
12
13
# File 'lib/nano/enumerable/%2A%2A.rb', line 11

def **(enum)
  Enumerable.cross(self, enum)
end

#collect_with_counterObject

Same as #collect but with an iteration counter.

a = [1,2,3].collect_with_counter{ |e,i| e*i }
a  #=> [0,2,6]

– Why the term counter? There may be a change in Ruby 2.0 to use this word instead of index. Index will still be used for Array, since that is the proper meaning in that context. In the mean time, aliases are provided. ++



13
14
15
16
17
18
19
# File 'lib/nano/enumerable/collect_with_counter.rb', line 13

def collect_with_counter
  r = []
  each_index do |i|
    r << yield(self[i], i)
  end
  r
end

#collect_with_indexObject



3
# File 'lib/nano/enumerable/collect_with_index.rb', line 3

alias_method( :collect_with_index, :collect_with_counter )

#commonality(&block) ⇒ Object

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. No guarantee about the order of the elements in the resulting array is made.

Note: The result will be an array if you supply no block and a hash otherwise.

[1, 2, 2, 3, 4, 4].commonality # => { 2 => [2], 4 => [4] }

["foo", "bar", "a"].commonality { |str| str.length }
# => { 2 => ["foo, "bar"] }

# Returns all persons that share their last name with another person.
persons.collisions { |person| person.last_name }

– Credit goes to Florian Gross ++



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/nano/enumerable/commonality.rb', line 22

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

#compact_collectObject

Collects/Maps and compacts items in one single step. The items for which the supplied block returns nil will not end up in the resulting array.

# Return telephone numbers of all persons that have a telephone number.
persons.compact_collect { |person| person.telephone_no }

Also see Enumerable#collect, Enumerable#map, Array#compact.



17
18
19
20
21
22
23
# File 'lib/nano/enumerable/compact_collect.rb', line 17

def compact_collect #:yield:
  filter_collect do |item|
    new_item = yield(item)
    throw(:skip) if new_item.nil?
    new_item
  end
end

#compact_mapObject



6
# File 'lib/nano/enumerable/compact_map.rb', line 6

alias_method :compact_map, :compact_collect

#cross(*enums, &block) ⇒ Object

The instance level cross-product method.

a = []
[1,2].cross([4,5]){|elem| a << elem }
a  #=> [[1, 4],[1, 5],[2, 4],[2, 5]]


14
15
16
# File 'lib/nano/enumerable/cross.rb', line 14

def cross(*enums, &block)
  Enumerable.cross(self, *enums, &block)
end

#each_byObject



4
# File 'lib/nano/enumerable/each_by.rb', line 4

alias_method( :each_by, :each_slice )

#each_slice(step = nil, &yld) ⇒ Object

Iterate through slices. If slicing step is not given, the the arity if the block is used.

x = []
[1,2,3,4].each_slice(2){ |a,b| x << [a,b] }
x  #=> [ [1,2], [3,4] ]

x = []
[1,2,3,4,5,6].each_slice(3){ |a| x << a }
x  #=> [ [1,2,3], [4,5,6] ]


15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/nano/enumerable/each_slice.rb', line 15

def each_slice(step=nil, &yld)
  if step
    cnt = step
    n = length / cnt
    n += 1 if length % cnt > 0
    n.times { |i|
      yld.call(*self.slice(i*cnt,cnt))
    }
    return cnt
  else
    cnt = yld.arity.abs
    n = length / cnt
    n += 1 if length % cnt > 0
    n.times { |i|
      yld.call(*self.slice(i*cnt,cnt))
    }
    return cnt
  end
end

#eFObject Also known as: %

Returns an elementwise Functor. This makes R-like elementwise operations possible.

a = [1,2,3]
b = [4,5]

p a.eF + 3       => [4,5,6]
p a.eF + b       => [5,7]
p a.eF.+(b,2)    => [[5,7],[3,4,5]]

NOTE: This method is still undergoing some fine tuning. It is questionable as to whether different sized arrays should be padded with nil (or a parameter) and whether multiple parameters are a good idea (eg. a.%.+(b,2,...)).

– Special thanks to Martin DeMello for helping to develop this. ++



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/nano/enumerable/eF.rb', line 34

def eF
  #Functor.new(self) do |op,*args|
  Functor.new do |op,*args|
    a = args.collect do |arg|
      if arg.kind_of?(Enumerable)
        ln = ( arg.length > self.length ? self.length : arg.length )
        self[0...ln].zip(arg[0...ln]).collect{ |a,b| a.send(op,b) }
        #self[0...ln].zip(arg[0...1n]).collect{ |a,b| b ? a.send(op,b) : nil }
      else
        self.collect{ |a| a.send(op,arg) }
      end
    end
    a.flatten! if args.length == 1
    a
  end
end

#entropyObject

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  #=>


15
16
17
18
19
20
21
22
23
# File 'lib/nano/enumerable/entropy.rb', line 15

def entropy
  arr = self.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

#filter_collectObject

Collects/Maps and filters items out in one single step. You can use throw(:skip) in the supplied block to indicate that the current item should not end up in the resulting array.

# Return names of all person with an age of at least 18.
persons.filter_collect do |person|
  throw(:skip) if person.age < 18
  person.name
end

Also see Enumerable#collect, Enumerable#find_all.



18
19
20
21
22
23
24
25
26
27
# File 'lib/nano/enumerable/filter_collect.rb', line 18

def filter_collect #:yield:
  result = []
  self.each do |item|
    catch(:skip) do
      new_item = yield(item)
      result << new_item
    end
  end
  return result
end

#filter_mapObject



6
# File 'lib/nano/enumerable/filter_map.rb', line 6

alias_method :filter_map, :filter_collect

#find_collisions(&blk) ⇒ Object

Like commonality but returns an array if no block is given.



7
8
9
10
11
12
13
# File 'lib/nano/enumerable/find_collisions.rb', line 7

def find_collisions( &blk ) #:yield:
  if block_given?
    commonality( &blk )
  else
    commonality.values.flatten.uniq
  end
end

#frequencyObject

Generates a hash mapping each unique symbol in the array to the absolute frequency it appears.



8
9
10
11
12
13
14
# File 'lib/nano/enumerable/frequency.rb', line 8

def frequency
  probs = Hash.new(0)
  each do | e |
    probs[e] += 1
  end
  probs
end

#graph(&yld) ⇒ Object

Like #map/#collect, but it 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 }

– Credits for original work goes to Zallus Kanite and Gavin Sinclair. ++



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/nano/enumerable/graph.rb', line 13

def graph(&yld)
  if yld
    inject({}) do |h,kv|
      nk, nv = yld[*kv]
      h[nk] = nv
      h
    end
  else
    Hash[*self.to_a.flatten]
  end
end

#ideal_entropyObject

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 goes to Derek. ++



12
13
14
15
16
# File 'lib/nano/enumerable/ideal_entropy.rb', line 12

def ideal_entropy
  arr = self.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

#map_with_counterObject



3
# File 'lib/nano/enumerable/map_with_counter.rb', line 3

alias_method( :map_with_counter, :collect_with_counter )

#map_with_indexObject



3
# File 'lib/nano/enumerable/map_with_index.rb', line 3

alias_method( :map_with_index, :collect_with_counter )

#none?Boolean

Enumerable#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, Enumerable#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

Returns:

  • (Boolean)


20
21
22
23
24
25
26
# File 'lib/nano/enumerable/none%3F.rb', line 20

def none?  # :yield: e
  if block_given?
    not self.any? { |e| yield e }
  else
    not self.any?
  end
end

#occur(n = nil) ⇒ Object

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]


17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/nano/enumerable/occur.rb', line 17

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?Boolean

Enumerable#one? returns true if and only if exactly one element in the collection satisfies the given predicate.

If no predicate is provided, Enumerable#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

Returns:

  • (Boolean)


20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/nano/enumerable/one%3F.rb', line 20

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

#partition_byObject

See Enumerable#partition for the background. #partition_by is best explained by example.

(1..5).partition_by { |n| n % 3 }
     #=> { 0 => [3], 1 => [1, 4], 2 => [2,5] } 

["I had", 1, "dollar and", 50, "cents"].partition_by { |e| e.class }
     #=> { String => ["I had","dollar and","cents"], Fixnum => [1,50] }

#partition_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. – Credit goes to Gavin Sinclair. ++



19
20
21
22
23
24
25
26
# File 'lib/nano/enumerable/partition_by.rb', line 19

def partition_by #:yield:
  result = {}
  self.each do |e|
    value = yield e
    (result[value] ||= []) << e
  end
  result
end

#probabilityObject

Generates a hash mapping each unique symbol in the array to the relative frequency, i.e. the probablity, of it appearence.



10
11
12
13
14
15
16
17
18
19
# File 'lib/nano/enumerable/probability.rb', line 10

def probability
  probs = Hash.new(0.0)
  size = 0.0
  each do | e |
    probs[e] += 1.0
    size += 1.0
  end
  probs.keys.each do |e| probs[e] /= size end
  probs
end

#to_hObject

Produces a hash from an Enumerable with index for keys.

a1 = [ :a, :b ]
a1.to_h  #=> { 0=>:a, 1=>:b }


8
9
10
11
12
# File 'lib/nano/enumerable/to_h.rb', line 8

def to_h
  h = {}
  each_with_index{ |e,i| h[i] = e }
  h
end

#uniq_byObject

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]


12
13
14
# File 'lib/nano/enumerable/unique_by.rb', line 12

def uniq_by #:yield:
  h = {}; inject([]) {|a,x| h[yield(x)] ||= a << x}
end