Module: Enumerable

Defined in:
lib/sixarm_ruby_ramp/enumerable.rb,
lib/sixarm_ruby_ramp/enumerable/map.rb,
lib/sixarm_ruby_ramp/enumerable/each.rb,
lib/sixarm_ruby_ramp/enumerable/nitems.rb,
lib/sixarm_ruby_ramp/enumerable/select.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.cartesian_product(*enums) ⇒ Array

This is the fastest implementation we have found. It returns results in typical order.

For our benchmarks, we also compared these:

Returns:

  • (Array)

    the cartesian product of the enumerations.

See Also:

Author:

  • Thomas Hafner



318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 318

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

Instance Method Details

#bisectArray<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.

Returns 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.

Examples:

enum.bisect {|obj| block}
=> array of positives, array of negatives

Returns:

  • (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.)

    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.



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 165

def bisect
  accept=[]
  reject=[]
  each{|item|
   if yield(item)
    accept << item
   else
    reject << item
   end
  }
  return accept,reject
end

#cartesian_product(*enums) ⇒ Object

Calculate the cartesian product.



334
335
336
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 334

def cartesian_product(*enums)
  Enumerable.cartesian_product(self,*enums)
end

#each_at(filter) ⇒ Object

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"


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/sixarm_ruby_ramp/enumerable/each.rb', line 31

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

#each_at_normalize_filter(filter) ⇒ Object

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.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/sixarm_ruby_ramp/enumerable/each.rb', line 132

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

#each_at_strategy_with_optimization_max(filter) ⇒ Object

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.



120
121
122
123
124
# File 'lib/sixarm_ruby_ramp/enumerable/each.rb', line 120

def each_at_strategy_with_optimization_max(filter)
  filter.each{|i|
    yield(at(i))
  }
end

#each_at_strategy_with_optimization_min(filter) ⇒ Object

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.



108
109
110
111
112
# File 'lib/sixarm_ruby_ramp/enumerable/each.rb', line 108

def each_at_strategy_with_optimization_min(filter)
  filter.each{|i|
    yield(at(i >= 0 ? i : i + self.size))
  }
end

#each_at_strategy_with_optimization_off(filter) ⇒ Object

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.



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/sixarm_ruby_ramp/enumerable/each.rb', line 87

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

#hash_byHash<Object,Object>

Convert the enumerable to a hash by mapping each item to a pair [item, new item]

Examples:

strings = ["red","blue","green"]
strings.hash_by{|a| [a.size, a.upcase]}
=> {3 => "RED", 4 => "BLUE", 5 => "GREEN"}

Returns:

See Also:



95
96
97
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 95

def hash_by
  map{|item| yield(item)}.to_h
end

#index_byHash<Integer,Object>

Convert the enumerable to a hash by mapping each item to a pair [index ,item]

Rails has this method.

From stackoverflow.com/questions/412771/cleanest-way-to-create-a-hash-from-an-array

Examples:

strings = ["red","blue","green"]
strings.index_by{|a| a.size]}
=> {3 => "red", 4 => "blue", 5 => "green"}

Returns:

See Also:



80
81
82
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 80

def index_by
  inject({}) {|hash, elem| hash.merge!(yield(elem) => elem) }
end

#intersect?(enum) ⇒ Boolean

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.

Examples:

['a','b','c'].intersect?(['c','d','e'] => true
['a','b','c'].intersect?(['d','e','f'] => false

Returns:

  • (Boolean)

    true if this enum intersects another enum.



300
301
302
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 300

def intersect?(enum)
  return enum.any?{|item| self.include?(item)}
end

#join(*op) ⇒ String

Shortcut to Array#join to concatenate the items into a string

Examples:

["foo", "goo", "hoo"].join
=> "foogoohoo"

Returns:

  • (String)

    concatenated string

See Also:



280
281
282
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 280

def join(*op)
 to_a.join(*op)
end

#map_idEnumerable<Object>

Map each item => item.id

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)

Examples:

users = User.find(:all)
users.map_id => [1,2,3,4,...]

Returns:



16
17
18
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 16

def map_id
  map{|item| item.id}
end

#map_to_aEnumberable<Array<Object>>

Map each item => item.to_a

This method is a faster way to get the same results as items.map(&:to_a)

Examples:

hashes = [{1=>2, 3=>4},{5=>6, 7=>8}]
hashes.map_to_a => [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

Returns:

  • (Enumberable<Array<Object>>)

    a list of each item.to_a



30
31
32
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 30

def map_to_a
  map{|item| item.to_a}
end

#map_to_fEnumerable<Float>

Map each item => item.to_f

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)

Examples:

strings = ["1","2","3"]
strings.map_to_f => [1.0, 2.0, 3.0]

Returns:

  • (Enumerable<Float>)

    a list of each item.to_f



46
47
48
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 46

def map_to_f
  map{|item| item.to_f}
end

#map_to_iEnumerable<Integer>

Map each item => item.to_i

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)

Examples:

strings = ["1","2","3"]
strings.map_to_i => [1, 2, 3]

Returns:



62
63
64
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 62

def map_to_i
  map{|item| item.to_i}
end

#map_to_sEnumerable<String>

Map each item => item.to_s

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)

Examples:

numbers = [1, 2, 3]
numbers.map_to_s => ["1", "2", "3"]

Returns:



78
79
80
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 78

def map_to_s
  map{|item| item.to_s}
end

#map_to_symEnumerable<Symbol>

Map each item => item.to_sym

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)

Examples:

strings = ["foo", "goo", "hoo"]
strings.map_to_sym => [:foo, :goo, :hoo]

Returns:



94
95
96
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 94

def map_to_sym
  map{|item| item.to_sym}
end

#map_with_indexEnumerable<Object>

Map each item and its index => a new output

Examples:

strings = ["a", "b", "c"]
strings.map_with_index{|string,index| "#{string}#{index}"}
 => ["a0, "b1", "c3"]

Returns:

See Also:

  • #map
  • #each_with_index


109
110
111
112
# File 'lib/sixarm_ruby_ramp/enumerable/map.rb', line 109

def map_with_index
  index=-1
  map{|item| index+=1; yield(item,index)}
end

#mutex?Boolean

Returns boolean true iff block is not false or nil, zero or one time.

Examples:

enum.mutex? {|obj| block}
=> true iff block is not false or nil, zero or one time

Returns:

  • (Boolean)

    boolean true iff block is not false or nil, zero or one time



190
191
192
193
194
195
196
197
198
199
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 190

def mutex?
  num = 0
  each{|item|
    if yield(item)
      num += 1
      if num > 1 then return false end
    end
  }
  return true
end

#nitems?(n) ⇒ Boolean

Returns true iff the block is not false or nil num times.

Examples:

enum.nitems?(n) {| obj | block }
 => true iff the block is not false or nil num times

Returns:

  • (Boolean)

    true iff the block is not false or nil num times



213
214
215
216
217
218
219
220
221
222
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 213

def nitems?(n)
  num = 0
  each{|item|
    if yield(item)
      num+=1
      if num > n then return false end
    end
  }
  return num==n
end

#nitems_untilInteger

Returns the number of leading elements for which block is false.

Examples:

enum.nitems_until {| obj | block }
=> number of items

Returns:

  • (Integer)

    the number of leading elements for which block is false.



242
243
244
245
246
247
248
249
250
251
252
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 242

def nitems_until
  num = 0
  each{|item|
    if yield(item)
      break
    else
      num+=1
    end
  }
  return num
end

#nitems_whileInteger

Returns the number of leading elements for which block is not false or nil.

Examples:

enum.nitems_while {| obj | block }
 => number of items

Returns:

  • (Integer)

    the number of leading elements for which block is not false or nil.



230
231
232
233
234
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 230

def nitems_while
  num = 0
  each{|item| yield(item) ? (num+=1) : break}
  return num
end

#nitems_with_indexInteger

Calls block with two arguments, the item and its index, for each item in enum.

Examples:

enum.nitems_with_index {|obj,i| block }
 => number of items

Returns:

  • (Integer)

    the number of leading elements for which block is true.



263
264
265
266
267
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 263

def nitems_with_index
  index = 0
  each{|item| yield(item,index) ? (index+=1) : break}
  return index
end

#power_setArray<Array<Object>>

Calculate the power set.

This implementation is from the blog post below:

Examples:

[1,2,3].power_set.sort
=>  [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]

Returns:

  • (Array<Array<Object>>)

    the power set: an array with all subsets of the enum’s elements.

See Also:



350
351
352
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 350

def power_set
  inject([[]]){|c,y|r=[];c.each{|i|r<<i;r<<i+[y]};r}
end

#select_untilArray<Object>

Returns the leading elements for which block is falsey.

Examples:

enum.select_until {|obj| block }
 => array

Returns:

  • (Array<Object>)

    the leading elements for which block is falsey.



123
124
125
126
127
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 123

def select_until
  arr = []
  each{|item| yield(item) ? break : (arr << item)}
  return arr
end

#select_whileArray<Object>

Returns the leading elements for which block is truthy.

Examples:

enum.select_while {|obj| block }
 => array

Returns:

  • (Array<Object>)

    the leading elements for which block is truthy.



111
112
113
114
115
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 111

def select_while
  arr = []
  each{|item| yield(item) ? (arr << item) : break}
  return arr
end

#select_with_indexArray<Object> the leading elements for which block is truthy.

Calls block with two arguments, the item and its index, for each item in enum.

Examples:

enum.select_with_index {|obj,i| block }
=> array

Returns:

  • (Array<Object> the leading elements for which block is truthy.)

    Array<Object> the leading elements for which block is truthy.



137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 137

def select_with_index
  index = 0
  arr = []
  each{|item|
    if yield(item,index)
      arr << item
      index+=1
    else
      break
    end
  }
  return arr
end

#to_hObject



22
23
24
25
26
27
28
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 22

def to_h
  hash={}
  each{|key,val|
    hash[key]=val
  }
  return hash
end

#to_h_mergeHash<Object,Object>

Convert an enumerable to a hash and merge values.

If a key occurs more than once, then this will automatically merge the values to an array of the keys’ values.

Examples:

array=[[:a, :b],[:c, :d],[:e, :f]]
array.to_h => {:a=>:b, :c=>:d, :e=>:f}
array=[[:a,:b],[:a,:c],[:a,:d]]
array.to_h => {:a=>[:b, :c, :d]}

Returns:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/sixarm_ruby_ramp/enumerable.rb', line 46

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