Ad
// takes: String; returns: [ [String,Int] ] (Strings in return value are single characters)
function frequencies(s) {
  const rd = new Map();
  for(const c of s) {
    rd.set(c, rd.has(c) ? rd.get(c) + 1 : 1);
  }
  return Array.from(rd);
}

// takes: [ [String,Int] ], String; returns: String (with "0" and "1")
function encode(freqs,s) {
  
    if(freqs.length <= 1) return null;
  
    const huff = new Huffman(freqs);
  
    return huff.encode(s);
}

// takes [ [String, Int] ], String (with "0" and "1"); returns: String
function decode(freqs,bits) {
  
    if(freqs.length <= 1) return null;
    const huff = new Huffman(freqs);
    
    return huff.decode(bits);
}

// implement a Human Class
class Huffman {
  constructor(freqs) {
    // tree: [....] 
    // {name, type, left, right, freq}
    this.root = this.create(freqs);
    this.charToBits = this.calculateCharToBits();
  }
  create(f) {
    const nodes = f.map(e => this.createNode(e[0], "leaf", null, null, e[1]));
    //console.log("nodes: ", nodes);
    const heap = createHeap(nodes, (a, b) => a.freq - b.freq);
    
    //console.log("create heap complete!!!")
    
    while(heap.getLength() > 1) {
      const left = heap.findMin();
      const right = heap.findMin();
      const newNode = this.createNode('', 'inner', left, right, left.freq + right.freq);
      heap.add(newNode);
    }
    
    /* return the root of the huffman tree */
    return heap.findMin();
  }
  
  /*
    calculateCharToBits
    return a Map, char as key, encoded bits as value
    {
      'a': '0001'
      ....
    }
  */
  calculateCharToBits() {
    const res = new Map();
    /*
      by pre traveling,to calculate 
    */
    function preOrder(root, prebits) {
      if(root.left === null && root.right === null) {
        res.set(root.name, prebits);
        return;
      }
      
      root.left != null && preOrder(root.left, prebits + '0');
      root.right != null && preOrder(root.right, prebits + '1');
    }
    
    preOrder(this.root, '');
    return res;
  }
  
  createNode(name, type, left, right, freq) {
    return {
      name, 
      type,
      left,
      right,
      freq
    };
  }
  /*  
    string  => 0001111
  */
  encode(s) {
    const res = [];
    for(const c of s) {
      res.push(this.charToBits.get(c));
    }
    return res.join('');
  }
  
  /*
    00001111 => string
  */
  decode(s) {
    const res = [];
    
    let cur = this.root;
    for(const c of s) {
      if(cur.left === null && cur.right === null) {
        /* 
          leaf node
        */
        
        res.push(cur.name);
        cur = this.root;
        
      }
      if(c === '0') {
        cur = cur.left;
      }else {
        cur = cur.right;
      }
    }
    if(cur.left === null && cur.right === null) {
      /* 
        leaf node
      */
      res.push(cur.name);
    }
    return res.join("");
    
  }
}

/*
  Node
  {
    name: 'a',
    type: 'leaf'|'inner',
    left: obj,
    right: obj
    freq: number
  }
  
  push all iniital Node created base on freqs to heap,
  1. if heap.length <= 1, end
  2. find two smallest freq Node
  3. remove from heap
  4. create a new Node base on these two Node and push it to head.
  3. go to 1
*/



/*
  test createHeap function
*/
function createHeap(nodes, compare) {


  /*
    utility functions
  */
  function parent(i) {
    return Math.floor((i-1)/2);
  }

  function left(i) {
    return 2 * i + 1;
  }
  function right(i) {
    return 2 * i + 2;
  }
  /*
    core functions
  */
  function topToDown(i) {
    //console.log("topToDown:", i);
    /*
      use to tiny the tree after removing the min node
    */
    let cur = i;
    let rightIdx = right(i);
    let leftIdx = left(i);
    while(leftIdx < nodes.length && (rightIdx < nodes.length && compare(nodes[cur], nodes[rightIdx]) > 0 || compare(nodes[cur], nodes[leftIdx]) > 0)) {
      //console.log("rightIdx: ", rightIdx,rightIdx < nodes.length ? nodes[rightIdx] : 'not have right');
      //console.log("leftIdx,", leftIdx, nodes[leftIdx]);
      const minIdx = rightIdx >= nodes.length || compare(nodes[leftIdx], nodes[rightIdx]) < 0 ? leftIdx : rightIdx;
      const tmp = nodes[minIdx];
      nodes[minIdx] = nodes[cur];
      nodes[cur] = tmp;

      cur = minIdx;
      rightIdx = right(cur);
      leftIdx = left(cur);
      
      //console.log("minIdx: ", minIdx);
    }
  }
  function downToTop(i) {
    /*
      use to create initial tree and add a new Node
    */
    let cur = i, parentIdx = parent(cur);
    while(cur != 0 && compare(nodes[parentIdx], nodes[cur]) > 0) {
      const tmp = nodes[parentIdx];
      nodes[parentIdx] = nodes[cur];
      nodes[cur] = tmp;

      cur = parentIdx;
      parentIdx = parent(cur);
    }

  }

  /*
    inital heap

    from (n-1 - 1)/2 node to 0 node
      top => down to generate a valid heap
  */
   let n = parent(nodes.length - 1);

   for(let i = n; i >= 0; --i) {
    topToDown(i);
   } 
  
  /*
    return obj
     {
       getLength: () => {}
       findMin: () => {}
       add: () => {}
     }
  */

  return {
    getLength: () => nodes.length,
    add: (node) => {
       nodes.push(node);
       downToTop(nodes.length - 1);
    },
    findMin: () => {
      //console.log("findMin: ");
      const res = nodes[0];
      nodes[0] = nodes[nodes.length - 1];
      nodes.pop();
      topToDown(0);
      return res;
    }
  }
}