Module: EnumUtils

Extended by:
EnumUtils
Included in:
EnumUtils
Defined in:
lib/enum_utils.rb,
lib/enum_utils/version.rb,
lib/enum_utils/exhaustible_iter.rb

Defined Under Namespace

Classes: ExhaustibleIter

Constant Summary collapse

VERSION =
'0.2.0'

Instance Method Summary collapse

Instance Method Details

#concat(*enums) ⇒ Object

Given N enums, return a new enum which lazily exhausts every enum.



226
227
228
229
230
231
232
233
234
235
# File 'lib/enum_utils.rb', line 226

def concat(*enums)
  return enum_for(__method__, *enums) unless block_given?

  enums
    .lazy
    .flat_map(&:lazy)
    .to_enum { enums.sum(&:size) if enums.all?(&:size) }.each do |v|
      yield(v)
    end
end

#round_robin(*enums, with_index: false) ⇒ Object

Given N enums, return all their values by taking one value from each enum in turn, until all are exhausted. If `with_index: true` is given, also return index of the enum that originated the value.

Examples:

## 3 enums:

EnumUtils.round_robin(
  [3,2].each,
  [1,3].each,
  [5,3,4].each
).to_a # => [3,1,5,2,3,3,4]

## 3 enums with index:

EnumUtils.round_robin(
  [3,2].each,
  [1,3].each,
  [5,3,4].each,
  with_index: true
).to_a # => [[3,0], [1,1], [5,2], [2,0], [3,1], [3,2], [4,2]]


203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/enum_utils.rb', line 203

def round_robin(*enums, with_index: false)
  unless block_given?
    return enum_for(__method__, *enums, with_index: with_index)
  end

  unless with_index
    return round_robin(*enums, with_index: true) { |v, _| yield v }
  end

  active = prepare_enums(enums)

  while active.any?
    pos = 0

    while pos < active.size
      iter, i = active[pos]
      yield(iter.next, i)
      iter.exhausted? ? active.delete_at(pos) : pos += 1
    end
  end
end

#sorted_intersection(*enums, compare: -> a, b { a <=> b }, degree: enums.size) ⇒ Object

Given N consistently-sorted enums, return unique values that appear in at least [degree] number of different enums, preserving the global order, until all enums are depleted. By default a value must appear in all of them.

Examples:

## 3 ascending enums:

EnumUtils.sorted_intersection(
  [1,2].each,
  [2,3].each,
  [1,2,5].each
).to_a # => [2]

## 3 descending enums:

EnumUtils.sorted_intersection(
  [2,1].each,
  [3,2].each,
  [5,2,1].each,
  compare: -> a, b { b <=> a } # reverse order comparison
).to_a # => [2]

## 3 ascending enums with degree 2:

EnumUtils.sorted_intersection(
  [1,2].each,
  [2,3].each,
  [1,2,5].each,
  degree: 2
).to_a # => [1, 2]


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
# File 'lib/enum_utils.rb', line 41

def sorted_intersection *enums,
  compare: -> a, b { a <=> b },
  degree: enums.size

  unless block_given?
    return enum_for(__method__, *enums, compare: compare, degree: degree)
  end

  active  = prepare_enums(enums)
  compare = compare_with_error_handling(compare)
  last_v  = []
  seen_i  = Set[]

  while active.size >= (degree - seen_i.size)
    (min_iter, min_iter_i), pos = pick_min(active, compare, demote: seen_i)
    value = min_iter.next

    if last_v.empty? || compare.(last_v[0], value) != 0
      last_v.replace [value]
      seen_i.replace [min_iter_i]
    else
      seen_i << min_iter_i
    end

    if seen_i.size == degree
      yield(value)
      last_v.replace []
      seen_i.replace []
    end

    active.delete_at(pos) if min_iter.exhausted?
  end
end

#sorted_merge(*enums, compare: -> a, b { a <=> b }, with_index: false) ⇒ Object

Given N consistently-sorted enums, return all their values while preserving the global order. If `with_index: true` is given, also return index in the enum that originated the value.

Examples:

## 3 ascending enums:

EnumUtils.sorted_merge(
  [1,2].each,
  [2,3].each,
  [1,2,5].each
).to_a # => [1,1,2,2,2,3,5]

## 3 descending enums:

EnumUtils.sorted_merge(
  [2,1].each,
  [3,2].each,
  [5,2,1].each,
  compare: -> a, b { b <=> a } # reverse order comparison
).to_a # => [5,3,2,2,2,1,1]

## 3 ascending enums with index:

EnumUtils.sorted_union(
  [1,2].each,
  [2,3].each,
  [1,2,5].each,
  with_index: true
).to_a # => [[1,0], [1,2], [2,0], [2,1], [2,2], [3,1], [5,2]]


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/enum_utils.rb', line 159

def sorted_merge(*enums, compare: -> a, b { a <=> b }, with_index: false)
  unless block_given?
    return \
      enum_for(__method__, *enums, compare: compare, with_index: with_index)
  end

  unless with_index
    return sorted_merge(*enums, compare: compare, with_index: true) { |v, _|
      yield v
    }
  end

  active  = prepare_enums(enums)
  compare = compare_with_error_handling(compare)

  while active.any?
    (min_iter, min_iter_i), pos = pick_min(active, compare)
    yield(min_iter.next, min_iter_i)
    active.delete_at(pos) if min_iter.exhausted?
  end
end

#sorted_union(*enums, compare: -> a, b { a <=> b }, with_index: false) ⇒ Object

Given N consistently-sorted enums, return all their unique values while preserving their global order. If `with_index: true` is given, also return index of the enum in which the corresponding value was first seen.

Examples:

## 3 ascending enums:

EnumUtils.sorted_union(
  [1,2].each,
  [2,3].each,
  [1,2,5].each
).to_a # => [1,2,3,5]

## 3 descending enums:

EnumUtils.sorted_union(
  [2,1].each,
  [3,2].each,
  [5,2,1].each,
  compare: -> a, b { b <=> a } # reverse order comparison
).to_a # => [5,3,2,1]

## 3 ascending enums with index:

EnumUtils.sorted_union(
  [1,2].each,
  [2,3].each,
  [1,2,5].each,
  with_index: true
).to_a # => [[1,0], [2,0], [3,1], [5,2]]


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/enum_utils.rb', line 106

def sorted_union(*enums, compare: -> a, b { a <=> b }, with_index: false)
  unless block_given?
    return \
      enum_for(__method__, *enums, compare: compare, with_index: with_index)
  end

  unless with_index
    return sorted_union(*enums, compare: compare, with_index: true) { |v, _|
      yield v
    }
  end

  last_v = []

  sorted_merge(*enums, compare: compare, with_index: with_index).each { |v, i|
    if last_v.empty? || v != last_v[0]
      yield(v, i)
      last_v.replace([v])
    end
  }
end