Module: Enumerable

Included in:
Browser::Cache, Epi::Slop, Epi::Slop::Commands, 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#*)


523
524
525
526
527
528
529
530
# File 'lib/epitools/core_ext/enumerable.rb', line 523

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.


535
536
537
# File 'lib/epitools/core_ext/enumerable.rb', line 535

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

#averageObject

Average the elements


228
229
230
231
232
233
234
235
# File 'lib/epitools/core_ext/enumerable.rb', line 228

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


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


363
364
365
# File 'lib/epitools/core_ext/enumerable.rb', line 363

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


459
460
461
462
463
464
465
466
467
# File 'lib/epitools/core_ext/enumerable.rb', line 459

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


542
543
544
# File 'lib/epitools/core_ext/enumerable.rb', line 542

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 }

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
348
349
350
# File 'lib/epitools/core_ext/enumerable.rb', line 320

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


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

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


436
437
438
439
440
# File 'lib/epitools/core_ext/enumerable.rb', line 436

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


474
475
476
# File 'lib/epitools/core_ext/enumerable.rb', line 474

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] ]

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

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


356
357
358
# File 'lib/epitools/core_ext/enumerable.rb', line 356

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]]

373
374
375
376
377
378
379
380
# File 'lib/epitools/core_ext/enumerable.rb', line 373

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])

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

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


391
392
393
# File 'lib/epitools/core_ext/enumerable.rb', line 391

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] ]

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

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


508
509
510
511
512
513
514
515
516
517
518
# File 'lib/epitools/core_ext/enumerable.rb', line 508

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


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

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.


448
449
450
# File 'lib/epitools/core_ext/enumerable.rb', line 448

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)

242
243
244
245
246
247
248
249
# File 'lib/epitools/core_ext/enumerable.rb', line 242

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] ]


398
399
400
401
# File 'lib/epitools/core_ext/enumerable.rb', line 398

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