Module: Enumerable

Included in:
Browser::Cache, ObjectSpace, Path, Trie
Defined in:
lib/epitools/core_ext/enumerable.rb

Instance Method Summary collapse

Instance Method Details

#*(other) ⇒ Object

Multiplies this Enumerable by something. (Same behaviour as Enumerator#*)


520
521
522
523
524
525
526
527
# File 'lib/epitools/core_ext/enumerable.rb', line 520

def *(other)
  case other
  when Integer, String
    to_enum * other
  when Enumerable
    to_enum.cross_product(other)
  end
end

#**(n) ⇒ Object

Multiplies this Enumerable by itself `n` times.


532
533
534
# File 'lib/epitools/core_ext/enumerable.rb', line 532

def **(n)
  [self].cycle(n).reduce(:*)
end

#averageObject

Average the elements


225
226
227
228
229
230
231
232
# File 'lib/epitools/core_ext/enumerable.rb', line 225

def average
  count = 0
  sum   = 0

  each { |e| count += 1; sum += e }

  sum / count.to_f
end

#blank?Boolean

'true' if the Enumerable has no elements

Returns:

  • (Boolean)

6
7
8
# File 'lib/epitools/core_ext/enumerable.rb', line 6

def blank?
  not any?
end

#combination(*args, &block) ⇒ Object

See: See Array#combination


360
361
362
# File 'lib/epitools/core_ext/enumerable.rb', line 360

def combination(*args, &block)
  to_a.combination(*args, &block)
end

#countsObject Also known as: count_by, group_counts

Counts how many instances of each object are in the collection, returning a hash. (Also optionally takes a block.)

eg: [:a, :b, :c, :c, :c, :c].counts #=> :b=>1, :c=>4


456
457
458
459
460
461
462
463
464
# File 'lib/epitools/core_ext/enumerable.rb', line 456

def counts
  h = Hash.of_integers
  if block_given?
    each { |x| h[yield x] += 1 }
  else
    each { |x| h[x] += 1 }
  end
  h
end

#cross_product(other) ⇒ Object Also known as: cross

Same behaviour as Enumerator#cross_product


539
540
541
# File 'lib/epitools/core_ext/enumerable.rb', line 539

def cross_product(other)
  to_enum.cross_product(other)
end

#foldl(methodname = nil, &block) ⇒ Object

Identical to “reduce” in ruby1.9 (or foldl in haskell.)

Example:

array.foldl{|a,b| a + b } == array[1..-1].inject(array[0]){|a,b| a + b }

317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/epitools/core_ext/enumerable.rb', line 317

def foldl(methodname=nil, &block)
  result = nil

  raise "Error: pass a parameter OR a block, not both!" unless !!methodname ^ block_given?

  if methodname

    each_with_index do |e,i|
      if i == 0
        result = e
        next
      end

      result = result.send(methodname, e)
    end

  else

    each_with_index do |e,i|
      if i == 0
        result = e
        next
      end

      result = block.call(result, e)
    end

  end

  result
end

#group_neighbours_by(&block) ⇒ Object Also known as: group_neighbors_by

Associative grouping; groups all elements who share something in common with each other. You supply a block which takes two elements, and have it return true if they are “neighbours” (eg: belong in the same group).

Example:

[1,2,5,6].group_neighbours_by { |a,b| b-a <= 1 } #=> [ [1,2], [5,6] ]

(Note: This is a very fast one-pass algorithm – therefore, the groups must be pre-sorted.)


410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/epitools/core_ext/enumerable.rb', line 410

def group_neighbours_by(&block)
  result = []
  cluster = [first]
  each_cons(2) do |a,b|
    if yield(a,b)
      cluster << b
    else
      result << cluster
      cluster = [b]
    end
  end

  result << cluster if cluster.any?

  result
end

#grouped_to_hObject Also known as: group_to_h, to_h_in_groups, to_h_grouped

Converts an array of 2-element key/value pairs into a Hash, grouped by key. (Like to_h, but the pairs can have duplicate keys.)


433
434
435
436
437
# File 'lib/epitools/core_ext/enumerable.rb', line 433

def grouped_to_h
  result = Hash.of_arrays
  each {|k,v| result[k] << v }
  result
end

#groupsObject Also known as: grouped

group_by the elements themselves


471
472
473
# File 'lib/epitools/core_ext/enumerable.rb', line 471

def groups
  group_by(&:self)
end

#map_recursively(max_depth = nil, current_depth = 0, parent = nil, &block) ⇒ Object Also known as: deep_map, recursive_map, map_recursive

The same as “map”, except that if an element is an Array or Enumerable, map is called recursively on that element. (Hashes are ignored because of the complications of block arguments and return values.)

Example:

[ [1,2], [3,4] ].deep_map{|e| e ** 2 } #=> [ [1,4], [9,16] ]

258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/epitools/core_ext/enumerable.rb', line 258

def map_recursively(max_depth=nil, current_depth=0, parent=nil, &block)
  return self if max_depth and (current_depth > max_depth)

  map do |obj|
    if obj == parent # infinite loop scenario!
      yield obj
    else
      case obj
      when String, Hash
        yield obj
      when Enumerable
        obj.map_recursively(max_depth, current_depth+1, self, &block)
      else
        yield obj
      end
    end
  end
end

#parallel_map(num_workers = 8, &block) ⇒ Object

Map elements of this Enumerable in parallel using a pool full of Threads

eg: repos.parallel_map { |repo| system “git pull #repo” }


187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/epitools/core_ext/enumerable.rb', line 187

def parallel_map(num_workers=8, &block)
  require 'thread'

  queue = Queue.new
  each { |e| queue.push e }

  Enumerator.new(queue.size) do |y|
    workers = (0...num_workers).map do
      Thread.new do
        begin
          while e = queue.pop(true)
            y << block.call(e)
          end
        rescue ThreadError
        end
      end
    end

    workers.map(&:join)
  end
end

#permutation(*args, &block) ⇒ Object

See: Array#permutation


353
354
355
# File 'lib/epitools/core_ext/enumerable.rb', line 353

def permutation(*args, &block)
  to_a.permutation(*args, &block)
end

#powersetObject

Returns the powerset of the Enumerable

Example:

[1,2].powerset #=> [[], [1], [2], [1, 2]]

370
371
372
373
374
375
376
377
# File 'lib/epitools/core_ext/enumerable.rb', line 370

def powerset
  return to_enum(:powerset) unless block_given?
  a = to_a
  (0...2**a.size).each do |bitmask|
    # the bit pattern of the numbers from 0..2^(elements)-1 can be used to select the elements of the set...
    yield a.select.with_index { |e, i| bitmask[i] == 1 }
  end
end

#reverseObject


43
44
45
# File 'lib/epitools/core_ext/enumerable.rb', line 43

def reverse
  to_a.reverse
end

#reverse_eachObject


52
53
54
# File 'lib/epitools/core_ext/enumerable.rb', line 52

def reverse_each
  to_a.reverse_each
end

#rle {|[count, last]| ... } ⇒ Object

run-length encode the array (returns an array of [count, element] pairs)

Yields:

  • ([count, last])

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/epitools/core_ext/enumerable.rb', line 479

def rle
  return to_enum(:rle) unless block_given?

  last   = nil
  result = []
  count  = 1

  each do |e|
    if last
      if last != e
        yield [count, last]
        count = 1
      else
        count += 1
      end
    end

    last = e
  end

  yield [count, last]
end

#rzip(other) ⇒ Object

Reverse zip (aligns the ends of two arrays, and zips them from right to left)

eg:

>> [5,39].rzip([:hours, :mins, :secs])
=> [ [:mins, 5], [:secs, 39] ]

Note: Like zip, it will pad the second array if it's shorter than the first


388
389
390
# File 'lib/epitools/core_ext/enumerable.rb', line 388

def rzip(other)
  reverse_each.zip(other.reverse_each).reverse_each
end

#select_recursively(max_depth = nil, current_depth = 0, parent = nil, &block) ⇒ Object Also known as: deep_select, recursive_select, select_recursive

The same as “select”, except that if an element is an Array or Enumerable, select is called recursively on that element.

Example:

[ [1,2], [3,4] ].select_recursively{|e| e % 2 == 0 } #=> [ [2], [4] ]

288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/epitools/core_ext/enumerable.rb', line 288

def select_recursively(max_depth=nil, current_depth=0, parent=nil, &block)
  return self if max_depth and (current_depth > max_depth)

  map do |obj|
    if obj == parent # infinite loop scenario!
      obj if yield obj
    else
      case obj
      when String, Hash
        obj if yield obj
      when Enumerable
        obj.deep_select(max_depth, current_depth+1, self, &block)
      else
        obj if yield obj
      end
    end
  end.compact
end

#skip(n) ⇒ Object

Skip the first n elements and return an Enumerator for the rest, or pass them in succession to the block, if given. This is like “drop”, but returns an enumerator instead of converting the whole thing to an array.


25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/epitools/core_ext/enumerable.rb', line 25

def skip(n)
  if block_given?
    each do |x|
      if n > 0
        n -= 1
      else
        yield x
      end
    end
  else
    to_enum(:skip, n)
  end
end

#sort_numericallyObject

Sort strings by their numerical values


505
506
507
508
509
510
511
512
513
514
515
# File 'lib/epitools/core_ext/enumerable.rb', line 505

def sort_numerically
  sort_by do |e|
    e = e.path if e.is_a? Path

    if e.is_a? String
      e.split(/(\d+)/).map { |s| s =~ /^\d+$/ ? s.to_i : s }
    else
      [e]
    end
  end
end

#split_after(matcher = nil, options = {}, &block) ⇒ Object

Split the array into chunks, cutting between the matched element and the next element.

Example:

[1,2,3,4].split_after{|e| e == 3 } #=> [ [1,2,3], [4] ]

137
138
139
140
141
# File 'lib/epitools/core_ext/enumerable.rb', line 137

def split_after(matcher=nil, options={}, &block)
  options[:after]             ||= true
  options[:include_boundary]  ||= true
  split_at(matcher, options, &block)
end

#split_at(matcher = nil, options = {}, &block) ⇒ Object

Split this enumerable into chunks, given some boundary condition. (Returns an array of arrays.)

Options:

:include_boundary => true  #=> include the element that you're splitting at in the results
                               (default: false)
:after => true             #=> split after the matched element (only has an effect when used with :include_boundary)
                               (default: false)
:once => flase             #=> only perform one split (default: false)

Examples:

[1,2,3,4,5].split{ |e| e == 3 }
#=> [ [1,2], [4,5] ]

"hello\n\nthere\n".each_line.split_at("\n").to_a
#=> [ ["hello\n"], ["there\n"] ]

[1,2,3,4,5].split(:include_boundary=>true) { |e| e == 3 }
#=> [ [1,2], [3,4,5] ]

chapters = File.read("ebook.txt").split(/Chapter \d+/, :include_boundary=>true)
#=> [ ["Chapter 1", ...], ["Chapter 2", ...], etc. ]

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/epitools/core_ext/enumerable.rb', line 80

def split_at(matcher=nil, options={}, &block)
  include_boundary = options[:include_boundary] || false

  if matcher.nil?
    boundary_test_proc = block
  else
    if matcher.is_a? Regexp
      boundary_test_proc = proc { |element| element =~ matcher }
    else
      boundary_test_proc = proc { |element| element == matcher }
    end
  end

  Enumerator.new do |yielder|
    current_chunk = []
    splits        = 0
    max_splits    = options[:once] == true ? 1 : options[:max_splits]

    each do |e|

      if boundary_test_proc.call(e) and (max_splits == nil or splits < max_splits)

        if current_chunk.empty? and not include_boundary
          next # hit 2 boundaries in a row... just keep moving, people!
        end

        if options[:after]
          # split after boundary
          current_chunk << e        if include_boundary   # include the boundary, if necessary
          yielder << current_chunk                         # shift everything after the boundary into the resultset
          current_chunk = []                              # start a new result
        else
          # split before boundary
          yielder << current_chunk                         # shift before the boundary into the resultset
          current_chunk = []                              # start a new result
          current_chunk << e        if include_boundary   # include the boundary, if necessary
        end

        splits += 1

      else
        current_chunk << e
      end

    end

    yielder << current_chunk if current_chunk.any?

  end
end

#split_before(matcher = nil, options = {}, &block) ⇒ Object

Split the array into chunks, cutting before each matched element.

Example:

[1,2,3,4].split_before{|e| e == 3 } #=> [ [1,2], [3,4] ]

149
150
151
152
# File 'lib/epitools/core_ext/enumerable.rb', line 149

def split_before(matcher=nil, options={}, &block)
  options[:include_boundary]  ||= true
  split_at(matcher, options, &block)
end

#split_between(&block) ⇒ Object Also known as: cut_between

Split the array into chunks, cutting between two elements.

Example:

[1,1,2,2].split_between{|a,b| a != b } #=> [ [1,1], [2,2] ]

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/epitools/core_ext/enumerable.rb', line 160

def split_between(&block)
  Enumerator.new do |yielder|
    current = []
    last    = nil

    each_cons(2) do |a,b|
      current << a
      if yield(a,b)
        yielder << current
        current = []
      end
      last = b
    end

    current << last unless last.nil?
    yielder << current
  end
end

#sum(&block) ⇒ Object Also known as: sum_by

Sum the elements


213
214
215
216
217
218
219
# File 'lib/epitools/core_ext/enumerable.rb', line 213

def sum(&block)
  if block_given?
    map(&block).reduce(:+)
  else
    reduce(:+)
  end
end

#to_iterObject Also known as: iter

Convert the array into a stable iterator (Iter) object.


445
446
447
# File 'lib/epitools/core_ext/enumerable.rb', line 445

def to_iter
  Iter.new(to_a)
end

#uniqObject Also known as: uniq_by

Lazily enumerate unique elements (WARNING: This can cause an infinite loop if you enumerate over a cycle,

since it will keep reading the input until it finds a unique element)

239
240
241
242
243
244
245
246
# File 'lib/epitools/core_ext/enumerable.rb', line 239

def uniq
  already_seen = Set.new

  Enumerator::Lazy.new(self) do |yielder, value|
    key = block_given? ? yield(value) : value
    yielder << value if already_seen.add?(key)
  end
end

#unzipObject

Does the opposite of #zip – converts [ [:a, 1], [:b, 2] ] to [ [:a, :b], [1, 2] ]


395
396
397
398
# File 'lib/epitools/core_ext/enumerable.rb', line 395

def unzip
  # TODO: make it work for arrays containing uneven-length contents
  to_a.transpose
end