Module: Enumerable

Includes:
Array::ToCSV
Included in:
Browser::Cache, Epi::Slop, Epi::Slop::Commands, ObjectSpace, Path, Trie
Defined in:
lib/epitools/core_ext/enumerable.rb

Instance Method Summary collapse

Methods included from Array::ToCSV

#to_csv, #to_tsv

Instance Method Details

#*(other) ⇒ Object

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


526
527
528
529
530
531
532
533
# File 'lib/epitools/core_ext/enumerable.rb', line 526

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.


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

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

#averageObject

Average the elements


231
232
233
234
235
236
237
238
# File 'lib/epitools/core_ext/enumerable.rb', line 231

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)

9
10
11
# File 'lib/epitools/core_ext/enumerable.rb', line 9

def blank?
  not any?
end

#combination(*args, &block) ⇒ Object

See: See Array#combination


366
367
368
# File 'lib/epitools/core_ext/enumerable.rb', line 366

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


462
463
464
465
466
467
468
469
470
# File 'lib/epitools/core_ext/enumerable.rb', line 462

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


545
546
547
# File 'lib/epitools/core_ext/enumerable.rb', line 545

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 }

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
351
352
353
# File 'lib/epitools/core_ext/enumerable.rb', line 323

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


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

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


439
440
441
442
443
# File 'lib/epitools/core_ext/enumerable.rb', line 439

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


477
478
479
# File 'lib/epitools/core_ext/enumerable.rb', line 477

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

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

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” }


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

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


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

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

376
377
378
379
380
381
382
383
# File 'lib/epitools/core_ext/enumerable.rb', line 376

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


46
47
48
# File 'lib/epitools/core_ext/enumerable.rb', line 46

def reverse
  to_a.reverse
end

#reverse_eachObject


55
56
57
# File 'lib/epitools/core_ext/enumerable.rb', line 55

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

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

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


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

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

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

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.


28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/epitools/core_ext/enumerable.rb', line 28

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


511
512
513
514
515
516
517
518
519
520
521
# File 'lib/epitools/core_ext/enumerable.rb', line 511

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

140
141
142
143
144
# File 'lib/epitools/core_ext/enumerable.rb', line 140

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

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
130
131
132
# File 'lib/epitools/core_ext/enumerable.rb', line 83

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

152
153
154
155
# File 'lib/epitools/core_ext/enumerable.rb', line 152

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

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

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


217
218
219
220
221
222
223
# File 'lib/epitools/core_ext/enumerable.rb', line 217

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.


451
452
453
# File 'lib/epitools/core_ext/enumerable.rb', line 451

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)

245
246
247
248
249
250
251
252
# File 'lib/epitools/core_ext/enumerable.rb', line 245

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


401
402
403
404
# File 'lib/epitools/core_ext/enumerable.rb', line 401

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