Heaps: Find the Running Median

  • + 0 comments

    Ruby solution with custom Binary Heap implementation (as there is no built-in Heap in Ruby's core lib)

    class AbstractBinHeap
      def initialize
        @store = []
      end
    
      def empty?
        @store.empty?
      end
    
      def size
        @store.size
      end
      alias length size
    
      def to_s
        tmp = dup
        order = []
        order << tmp.pop until tmp.empty?
        order.join(' ')
      end
      alias inspect to_s
    
      def add(e)
        @store << e
        heapify_up(@store.size - 1)
      end
      alias << add
    
      def peek
        if empty?
          nil
        else
          @store[0]
        end
      end
      alias next peek
    
      def pop
        return nil if empty?
    
        e = @store[0]
        @store[0] = @store[-1]
        @store.pop
        heapify_down(0)
        e
      end
    
      # Delete first occurence of e.
      def delete(e)
        pos = @store.index(e)
        if pos.nil?
          nil
        else
          delete_at(pos)
        end
      end
    
      protected
    
      attr_writer :store
    
      def dup
        cl = self.class.new
        cl.store = @store.dup
        cl
      end
    
      private
    
      def delete_at(pos)
        return nil if pos >= @store.length
    
        e = @store[pos]
        @store[pos] = most_sig_val
        heapify_up(pos)
        pop
        e
      end
    
      def parent(pos)
        (pos - 1) / 2
      end
    
      def lchild(pos)
        pos * 2 + 1
      end
    
      def rchild(pos)
        pos * 2 + 2
      end
    
      def heapify_up(pos)
        return if pos == 0 || pos >= @store.length
    
        loop do
          parent = (pos - 1) / 2
          break unless pos > 0 && compare(@store[pos], @store[parent])
    
          @store[pos], @store[parent] = @store[parent], @store[pos]
          pos = parent
        end
      end
    
      def heapify_down(pos)
        return if pos >= @store.length
    
        while lchild(pos) < @store.length
          lchild = lchild(pos)
          rchild = rchild(pos)
          minchild = if rchild < @store.length
                       compare(@store[lchild], @store[rchild]) ? lchild : rchild
                     else
                       lchild
                     end
    
          if compare(@store[minchild], @store[pos])
            @store[pos], @store[minchild] = @store[minchild], @store[pos]
            pos = minchild
          else
            break
          end
        end
      end
    end
    
    class MinHeap < AbstractBinHeap
      private
    
      def compare(obj1, obj2)
        obj1 < obj2
      end
    
      def most_sig_val
        -1 * Float::INFINITY
      end
    end
    
    class MaxHeap < AbstractBinHeap
      private
    
      def compare(obj1, obj2)
        obj1 > obj2
      end
    
      def most_sig_val
        Float::INFINITY
      end
    end
    
    def running_median(arr)
      smaller = MaxHeap.new
      larger = MinHeap.new
      arr.map do |e|
        if smaller.empty? || e <= smaller.peek
          smaller << e
        else
          larger << e
        end
    
        if smaller.size < larger.size
          smaller << larger.pop
        elsif smaller.size - 1 > larger.size
          larger << smaller.pop
        end
    
        if larger.empty?
          smaller.peek
        else
          smaller.size == larger.size ? (smaller.peek + larger.peek) / 2 : smaller.peek
        end
      end
    end
    
    n = gets.to_i
    arr = Array.new(n)
    n.times { |i| arr[i] = gets.to_f }
    puts(running_median(arr).map { |m| format('%.1f', m) })