【问题标题】:Merge N sorted arrays in ruby lazily懒惰地在ruby中合并N个排序的数组
【发布时间】:2011-08-12 15:00:57
【问题描述】:

如何在 Ruby 中懒惰地合并 N 个已排序的数组(或其他类似列表的数据结构)?例如,在 Python 中,您将使用 heapq.merge。 Ruby 中一定有类似的东西,对吧?

【问题讨论】:

    标签: ruby lazy-evaluation


    【解决方案1】:

    这是一个(略带高尔夫球的)解决方案,它应该适用于支持 #first#shift#empty? 的任何“类似列表”集合的数组。请注意,它具有破坏性 - 每次调用 lazymerge 都会从一个集合中删除一项。

    def minheap a,i
      r=(l=2*(m=i)+1)+1 #get l,r index
      m = l if l< a.size and a[l].first < a[m].first
      m = r if r< a.size and a[r].first < a[m].first
      (a[i],a[m]=a[m],a[i];minheap(a,m)) if (m!=i)
    end
    
    
    def lazymerge a
      (a.size/2).downto(1){|i|minheap(a,i)}
      r = a[0].shift
      a[0]=a.pop if a[0].empty?
      return r
    end
    
    p arrs = [ [1,2,3], [2,4,5], [4,5,6],[3,4,5]]
    v=true
    puts "Extracted #{v=lazymerge (arrs)}.  Arr= #{arrs.inspect}" while v
    

    输出:

    [[1, 2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
    Extracted 1.  Arr= [[2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
    Extracted 2.  Arr= [[3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
    Extracted 2.  Arr= [[4, 5], [3], [4, 5, 6], [3, 4, 5]]
    Extracted 3.  Arr= [[4, 5], [3, 4, 5], [4, 5, 6]]
    Extracted 3.  Arr= [[4, 5], [4, 5], [4, 5, 6]]
    Extracted 4.  Arr= [[5], [4, 5], [4, 5, 6]]
    Extracted 4.  Arr= [[5], [5], [4, 5, 6]]
    Extracted 4.  Arr= [[5, 6], [5], [5]]
    Extracted 5.  Arr= [[6], [5], [5]]
    Extracted 5.  Arr= [[5], [6]]
    Extracted 5.  Arr= [[6]]
    Extracted 6.  Arr= [[]]
    Extracted .  Arr= [[]]
    

    还请注意,此算法在维护堆属性方面也很懒惰 - 它不会在调用之间进行维护。这可能会导致它做比需要更多的工作,因为它会在每个后续调用中完成一个完整的 heapify。这可以通过预先进行一次完整的 heapify 来解决,然后在 return r 行之前调用 minheap(a,0)

    【讨论】:

      【解决方案2】:

      我最终使用“算法”gem 中的数据结构自己编写了它。并没有我想象的那么糟糕。

      require 'algorithms'
      
      class LazyHeapMerger
        def initialize(sorted_arrays)
          @heap = Containers::Heap.new { |x, y| (x.first <=> y.first) == -1 }
          sorted_arrays.each do |a|
            q = Containers::Queue.new(a)
            @heap.push([q.pop, q])
          end
        end
      
        def each
          while @heap.length > 0
            value, q = @heap.pop
            @heap.push([q.pop, q]) if q.size > 0
            yield value
          end
        end
      end
      
      m = LazyHeapMerger.new([[1, 2], [3, 5], [4]])
      m.each do |o|
        puts o
      end
      

      【讨论】:

      • 保留队列上下文的有趣方法。唯一的缺点是代码为每个值拉取创建一个新的 Array 对象,这为垃圾收集器工作。
      【解决方案3】:

      这是一个适用于任何 Enumerable 甚至无限 Enumerable 的实现。它返回枚举器。

      def lazy_merge *list
        list.map!(&:enum_for) # get an enumerator for each collection
        Enumerator.new do |yielder|
          hash = list.each_with_object({}){ |enum, hash|
            begin
              hash[enum] = enum.next
            rescue StopIteration
              # skip empty enumerators
            end
          }
          loop do
            raise StopIteration if hash.empty?
      
            enum, value = hash.min_by{|k,v| v}
            yielder.yield value
            begin
              hash[enum] = enum.next
            rescue StopIteration
              hash.delete(enum) # remove enumerator that we already processed
            end
          end
        end
      end
      
      Infinity = 1.0/0 # easy way to get infinite range
      
      p lazy_merge([1, 3, 5, 8], (2..4), (6..Infinity), []).take(12)
      #=> [1, 2, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10]
      

      【讨论】:

        【解决方案4】:

        不,没有任何内置功能可以做到这一点。至少,没有什么会立即浮现在脑海中。但是,几年前有一个GSoC project 来实现相关的数据类型,您可以使用它。

        【讨论】:

        • 看起来堆可以工作,只是它不是懒惰的。太糟糕了,不过还是谢谢你的建议,后面的其他算法可能会派上用场。
        猜你喜欢
        • 2012-06-07
        • 1970-01-01
        • 2019-06-20
        • 2010-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-11
        • 2012-02-20
        相关资源
        最近更新 更多