Heaps: Find the Running Median

  • + 0 comments

    Solution with my original Heap class and an optimized way faster one:

    class Heap { // My original implementation - cagils
      constructor(comp) {
        this.heap = [];
        if (comp === "max" || !comp) this.comp = (a, b) => a > b;
        else if (comp === "min") this.comp = (a, b) => a < b;
        else this.comp = comp;
      }
      _heapify() {
        for (let i = parseInt(this.heap.length / 2 - 1); i >= 0; i--)
          Heap.heapify(this.heap, this.heap.length, i, this.comp);
      }
      static heapify(arr, n, i, comp) {
        let first = i;
        let li = 2*i+1;
        let ri = 2*i+2;
        if (li < n && comp(arr[li], arr[first])) first = li;
        if (ri < n && comp(arr[ri], arr[first])) first = ri;
        if (first !== i) {
          [arr[i], arr[first]] = [arr[first], arr[i]];
          Heap.heapify(arr, n, first, comp);
        }
      }
      push = (data) => {
        this.heap.push(data);
        this._heapify();
      };
      remove(data) {
        const size = this.heap.length;
        let i = this.heap.findIndex((e) => e === data);
        if (i === -1) i = size;
        [this.heap[i], this.heap[size - 1]] = [this.heap[size - 1], this.heap[i]];
        this.heap.splice(size - 1);
        this._heapify();
        return data;
      }
      peek = () => this.heap[0];
      pop = () => this.remove(this.heap[0]);
      size = () => this.heap.length;
      getList = () => this.heap;
    }
    
    class _Heap { // faster implementation
        constructor(comp) {
            this.a = [];
            if (comp === "min" || !comp) this.comp = (a, b) => a < b;
            else if (comp === "max") this.comp = (a, b) => a > b;
            else this.comp = comp;
        }
        
        up (i) {
            //const pi = (i - 1) >> 1;//Math.floor((i - 1) / 2)
            const pi = Math.floor((i - 1) / 2);
            if(this.comp(this.a[i], this.a[pi])) {
                [this.a[pi], this.a[i]] = [this.a[i], this.a[pi]];
                this.up(pi);
            }
        }
        
        down (i) {
            const lci = i * 2 + 1;
            const rci = lci + 1;
            const pi = i;
            const a_i = this.a[i];
            if(this.comp(this.a[lci], a_i) || this.comp(this.a[rci], a_i)) {
                if(this.comp(this.a[lci], a_i) && this.comp(this.a[rci], a_i))
                    i = this.comp(this.a[lci], this.a[rci]) ? lci : rci;
                else
                    i = this.comp(this.a[lci], a_i) ? lci : rci;
                this.a[pi] = this.a[i];
                this.a[i] = a_i;
                this.down(i);
            }
        }
        
        push(num) {
            this.a.push(num);
            this.up(this.a.length - 1);
        }
        
        pop() {
            const deletedVal = this.a[0];
            this.a.length > 1 ? this.a[0] = this.a.pop() : this.a.pop();
            this.down(0);
            return deletedVal;
        }
        
        peek() { return this.a[0] }
        size = () => this.a.length;
        getList = () => this.a;
    }
    
    function runningMedian(a) {
        const highers = new _Heap('min');
        const lowers = new _Heap('max');
        
        for (const n of a) {
            if (lowers.size() === 0 || lowers.peek() > n) lowers.push(n); else highers.push(n);        
            let [big, small] = highers.size() > lowers.size() ? [highers, lowers] : [lowers, highers];
            let diff = big.size() - small.size();
            if (big.size() - small.size() > 1) {
                small.push(big.pop());
                diff = 0;
            }
            const out = diff === 0 ? (big.peek() + small.peek()) / 2 : big.peek();
            console.log(out.toFixed(1))
        }
    }