Iterate over sorted enumerables preserving order

# Iterate over sorted enumerables preserving order
# across all of them yielding items in globally-sorted order.
def sorted_iterate(*enums, with_index: false)
  unless block_given?
    return enum_for(__method__, *enums, with_index: with_index)
  end

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

  index = enums.map.with_index { |e, i| [i, e] }.to_h

  loop do
    peeked = []

    index.select! do |i, e|
      peeked << [e.peek, e, i]
    rescue StopIteration
      false
    end

    break if peeked.empty?
    min = peeked.min_by(&:first)
    yield min[1].next, min[2]
  end
end

Better optimized, no indexes

# If you have multiple sorted enumerables, this function will lazy-iterate
# across all of them yielding items in globally-sorted order.
def sorted_iterate(*enums)
  return enum_for(__method__, *enums) unless block_given?

  buffers = enums.map { |e| [e, []] }

  loop do
    buffers.select! do |enum, value|
      !value.empty? || (value << enum.next)
    rescue StopIteration
      false
    end

    break if buffers.empty?
    yield buffers.min_by(&:last)[1].pop
  end
end

Code snippets in this post are covered by 0BSD License.


Tags
ruby snippet

Date
October 30, 2022