From 7715bcb077221bc7cbf053348916c9869d92e4da Mon Sep 17 00:00:00 2001
From: appleJax <appleJax@users.noreply.github.com>
Date: Tue, 28 Aug 2018 16:40:07 -0500
Subject: [PATCH 1/3] Add support for complex data to PriorityQueue

---
 .../priority-queue/PriorityQueue.js           |  73 ++++++----
 .../__test__/PriorityQueue.test.js            | 136 +++++++++++-------
 2 files changed, 132 insertions(+), 77 deletions(-)

diff --git a/src/data-structures/priority-queue/PriorityQueue.js b/src/data-structures/priority-queue/PriorityQueue.js
index 2bf27bb33a..1edd5875b4 100644
--- a/src/data-structures/priority-queue/PriorityQueue.js
+++ b/src/data-structures/priority-queue/PriorityQueue.js
@@ -4,19 +4,26 @@ import Comparator from '../../utils/comparator/Comparator';
 // It is the same as min heap except that when comparing to elements
 // we take into account not element's value but rather its priority.
 export default class PriorityQueue extends MinHeap {
-  constructor() {
+  /**
+   * @constructor
+   * @param {function|undefined} compareValueFunction
+   */
+  constructor(compareValueFunction) {
     super();
-    this.priorities = {};
+    // Map data structure supports using any value for key type
+    // e.g. functions, objects, or primitives
+    this.priorities = new Map();
     this.compare = new Comparator(this.comparePriority.bind(this));
+    this.compareValue = new Comparator(compareValueFunction);
   }
 
   /**
    * @param {*} item
-   * @param {number} [priority]
+   * @param {number} priority
    * @return {PriorityQueue}
    */
   add(item, priority = 0) {
-    this.priorities[item] = priority;
+    this.priorities.set(item, priority);
     super.add(item);
 
     return this;
@@ -24,12 +31,13 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
-   * @param {Comparator} [customFindingComparator]
+   * @param {Comparator|function|undefined} maybeComparator
    * @return {PriorityQueue}
    */
-  remove(item, customFindingComparator) {
-    super.remove(item, customFindingComparator);
-    delete this.priorities[item];
+  remove(item, maybeComparator) {
+    const comparator = this.getValueComparator(maybeComparator);
+    super.remove(item, comparator);
+    this.priorities.delete(item);
 
     return this;
   }
@@ -37,10 +45,12 @@ export default class PriorityQueue extends MinHeap {
   /**
    * @param {*} item
    * @param {number} priority
+   * @param {Comparator|function|undefined} maybeComparator
    * @return {PriorityQueue}
    */
-  changePriority(item, priority) {
-    this.remove(item, new Comparator(this.compareValue));
+  changePriority(item, priority, maybeComparator) {
+    const comparator = this.getValueComparator(maybeComparator);
+    this.remove(item, comparator);
     this.add(item, priority);
 
     return this;
@@ -48,31 +58,46 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
+   * @param {Comparator|function|undefined} maybeComparator
    * @return {Number[]}
    */
-  findByValue(item) {
-    return this.find(item, new Comparator(this.compareValue));
+  findByValue(item, maybeComparator) {
+    const comparator = this.getValueComparator(maybeComparator);
+    return this.find(item, comparator);
   }
 
   /**
    * @param {*} item
+   * @param {Comparator|function|undefined} maybeComparator
    * @return {boolean}
    */
-  hasValue(item) {
-    return this.findByValue(item).length > 0;
+  hasValue(item, maybeComparator) {
+    const comparator = this.getValueComparator(maybeComparator);
+    return this.findByValue(item, comparator).length > 0;
   }
 
   /**
-   * @param {*} a
-   * @param {*} b
-   * @return {number}
+   * @param {Comparator|function|undefined} maybeComparator
+   * @return {Comparator}
    */
-  comparePriority(a, b) {
-    if (this.priorities[a] === this.priorities[b]) {
-      return 0;
+  getValueComparator(maybeComparator) {
+    if (maybeComparator == null) {
+      return this.compareValue;
+    }
+
+    if (maybeComparator instanceof Comparator) {
+      return maybeComparator;
     }
 
-    return this.priorities[a] < this.priorities[b] ? -1 : 1;
+    if (maybeComparator instanceof Function) {
+      return new Comparator(maybeComparator);
+    }
+
+    throw new TypeError(
+      'Invalid comparator type\n'
+      + 'Must be one of: Comparator | Function | undefined\n'
+      + `Given: ${typeof maybeComparator}`,
+    );
   }
 
   /**
@@ -80,11 +105,11 @@ export default class PriorityQueue extends MinHeap {
    * @param {*} b
    * @return {number}
    */
-  compareValue(a, b) {
-    if (a === b) {
+  comparePriority(a, b) {
+    if (this.priorities.get(a) === this.priorities.get(b)) {
       return 0;
     }
 
-    return a < b ? -1 : 1;
+    return this.priorities.get(a) < this.priorities.get(b) ? -1 : 1;
   }
 }
diff --git a/src/data-structures/priority-queue/__test__/PriorityQueue.test.js b/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
index 264893d393..ff78771fd1 100644
--- a/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
+++ b/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
@@ -1,5 +1,11 @@
 import PriorityQueue from '../PriorityQueue';
 
+const JOB1 = { type: 'job1' };
+const JOB2 = { type: 'job2' };
+const JOB3 = { type: 'job3' };
+const JOB4 = { type: 'job4' };
+const JOB5 = { type: 'job5' };
+
 describe('PriorityQueue', () => {
   it('should create default priority queue', () => {
     const priorityQueue = new PriorityQueue();
@@ -10,94 +16,118 @@ describe('PriorityQueue', () => {
   it('should insert items to the queue and respect priorities', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    expect(priorityQueue.peek()).toBe(10);
+    priorityQueue.add(JOB1, 1);
+    expect(priorityQueue.peek()).toBe(JOB1);
 
-    priorityQueue.add(5, 2);
-    expect(priorityQueue.peek()).toBe(10);
+    priorityQueue.add(JOB2, 2);
+    expect(priorityQueue.peek()).toBe(JOB1);
 
-    priorityQueue.add(100, 0);
-    expect(priorityQueue.peek()).toBe(100);
+    priorityQueue.add(JOB3, 0);
+    expect(priorityQueue.peek()).toBe(JOB3);
   });
 
   it('should poll from queue with respect to priorities', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    priorityQueue.add(5, 2);
-    priorityQueue.add(100, 0);
-    priorityQueue.add(200, 0);
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+    priorityQueue.add(JOB4, 0);
 
-    expect(priorityQueue.poll()).toBe(100);
-    expect(priorityQueue.poll()).toBe(200);
-    expect(priorityQueue.poll()).toBe(10);
-    expect(priorityQueue.poll()).toBe(5);
+    expect(priorityQueue.poll()).toBe(JOB3);
+    expect(priorityQueue.poll()).toBe(JOB4);
+    expect(priorityQueue.poll()).toBe(JOB1);
+    expect(priorityQueue.poll()).toBe(JOB2);
   });
 
   it('should be possible to change priority of internal nodes', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    priorityQueue.add(5, 2);
-    priorityQueue.add(100, 0);
-    priorityQueue.add(200, 0);
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+    priorityQueue.add(JOB4, 0);
 
-    priorityQueue.changePriority(100, 10);
-    priorityQueue.changePriority(10, 20);
+    priorityQueue.changePriority(JOB4, 10);
+    priorityQueue.changePriority(JOB1, 20);
 
-    expect(priorityQueue.poll()).toBe(200);
-    expect(priorityQueue.poll()).toBe(5);
-    expect(priorityQueue.poll()).toBe(100);
-    expect(priorityQueue.poll()).toBe(10);
+    expect(priorityQueue.poll()).toBe(JOB3);
+    expect(priorityQueue.poll()).toBe(JOB2);
+    expect(priorityQueue.poll()).toBe(JOB4);
+    expect(priorityQueue.poll()).toBe(JOB1);
   });
 
   it('should be possible to change priority of head node', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    priorityQueue.add(5, 2);
-    priorityQueue.add(100, 0);
-    priorityQueue.add(200, 0);
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+    priorityQueue.add(JOB4, 0);
 
-    priorityQueue.changePriority(200, 10);
-    priorityQueue.changePriority(10, 20);
+    priorityQueue.changePriority(JOB3, 10);
+    priorityQueue.changePriority(JOB1, 20);
 
-    expect(priorityQueue.poll()).toBe(100);
-    expect(priorityQueue.poll()).toBe(5);
-    expect(priorityQueue.poll()).toBe(200);
-    expect(priorityQueue.poll()).toBe(10);
+    expect(priorityQueue.poll()).toBe(JOB4);
+    expect(priorityQueue.poll()).toBe(JOB2);
+    expect(priorityQueue.poll()).toBe(JOB3);
+    expect(priorityQueue.poll()).toBe(JOB1);
   });
 
   it('should be possible to change priority along with node addition', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    priorityQueue.add(5, 2);
-    priorityQueue.add(100, 0);
-    priorityQueue.add(200, 0);
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+    priorityQueue.add(JOB4, 0);
 
-    priorityQueue.changePriority(200, 10);
-    priorityQueue.changePriority(10, 20);
+    priorityQueue.changePriority(JOB4, 10);
+    priorityQueue.changePriority(JOB1, 20);
 
-    priorityQueue.add(15, 15);
+    priorityQueue.add(JOB5, 15);
 
-    expect(priorityQueue.poll()).toBe(100);
-    expect(priorityQueue.poll()).toBe(5);
-    expect(priorityQueue.poll()).toBe(200);
-    expect(priorityQueue.poll()).toBe(15);
-    expect(priorityQueue.poll()).toBe(10);
+    expect(priorityQueue.poll()).toBe(JOB3);
+    expect(priorityQueue.poll()).toBe(JOB2);
+    expect(priorityQueue.poll()).toBe(JOB4);
+    expect(priorityQueue.poll()).toBe(JOB5);
+    expect(priorityQueue.poll()).toBe(JOB1);
   });
 
   it('should be possible to search in priority queue by value', () => {
     const priorityQueue = new PriorityQueue();
 
-    priorityQueue.add(10, 1);
-    priorityQueue.add(5, 2);
-    priorityQueue.add(100, 0);
-    priorityQueue.add(200, 0);
-    priorityQueue.add(15, 15);
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+    priorityQueue.add(JOB4, 0);
+    priorityQueue.add(JOB5, 15);
+
+    const job6 = { type: 'job6' };
+
+    expect(priorityQueue.hasValue(job6)).toBe(false);
+    expect(priorityQueue.hasValue(JOB5)).toBe(true);
+  });
+
+  it('should accept a custom compareValue function', () => {
+    const compareByType = (a, b) => {
+      if (a.type === b.type) {
+        return 0;
+      }
+
+      return a.type < b.type ? -1 : 1;
+    };
+
+    const priorityQueue = new PriorityQueue(compareByType);
+
+    priorityQueue.add(JOB1, 1);
+    priorityQueue.add(JOB2, 2);
+    priorityQueue.add(JOB3, 0);
+
+    const existingJobType = { type: 'job1' };
+    const newJobType = { type: 'job4' };
 
-    expect(priorityQueue.hasValue(70)).toBe(false);
-    expect(priorityQueue.hasValue(15)).toBe(true);
+    expect(priorityQueue.hasValue(existingJobType)).toBe(true);
+    expect(priorityQueue.hasValue(newJobType)).toBe(false);
   });
 });

From de57051cd2ddd6fc3318c88505e239fb987c19b9 Mon Sep 17 00:00:00 2001
From: appleJax <appleJax@users.noreply.github.com>
Date: Thu, 30 Aug 2018 02:17:33 -0500
Subject: [PATCH 2/3] add ability to change priority for a group of elements

---
 src/data-structures/heap/Heap.js              | 64 ++++++++++++-------
 .../priority-queue/PriorityQueue.js           | 18 +++++-
 .../__test__/PriorityQueue.test.js            | 29 +++++++++
 3 files changed, 86 insertions(+), 25 deletions(-)

diff --git a/src/data-structures/heap/Heap.js b/src/data-structures/heap/Heap.js
index 45dfcfa267..7ecce9ab58 100644
--- a/src/data-structures/heap/Heap.js
+++ b/src/data-structures/heap/Heap.js
@@ -155,31 +155,40 @@ export default class Heap {
       // We need to find item index to remove each time after removal since
       // indices are being changed after each heapify process.
       const indexToRemove = this.find(item, comparator).pop();
+      this.removeIndex(indexToRemove);
+    }
+
+    return this;
+  }
 
-      // If we need to remove last child in the heap then just remove it.
-      // There is no need to heapify the heap afterwards.
-      if (indexToRemove === (this.heapContainer.length - 1)) {
-        this.heapContainer.pop();
+  /**
+   * @param {number} indexToRemove
+   * @return {Heap}
+   */
+  removeIndex(indexToRemove) {
+    // If we need to remove last child in the heap then just remove it.
+    // There is no need to heapify the heap afterwards.
+    if (indexToRemove === (this.heapContainer.length - 1)) {
+      this.heapContainer.pop();
+    } else {
+      // Move last element in heap to the vacant (removed) position.
+      this.heapContainer[indexToRemove] = this.heapContainer.pop();
+
+      // Get parent.
+      const parentItem = this.parent(indexToRemove);
+
+      // If there is no parent or parent is in correct order with the node
+      // we're going to delete then heapify down. Otherwise heapify up.
+      if (
+        this.hasLeftChild(indexToRemove)
+        && (
+          !parentItem
+          || this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove])
+        )
+      ) {
+        this.heapifyDown(indexToRemove);
       } else {
-        // Move last element in heap to the vacant (removed) position.
-        this.heapContainer[indexToRemove] = this.heapContainer.pop();
-
-        // Get parent.
-        const parentItem = this.parent(indexToRemove);
-
-        // If there is no parent or parent is in correct order with the node
-        // we're going to delete then heapify down. Otherwise heapify up.
-        if (
-          this.hasLeftChild(indexToRemove)
-          && (
-            !parentItem
-            || this.pairIsInCorrectOrder(parentItem, this.heapContainer[indexToRemove])
-          )
-        ) {
-          this.heapifyDown(indexToRemove);
-        } else {
-          this.heapifyUp(indexToRemove);
-        }
+        this.heapifyUp(indexToRemove);
       }
     }
 
@@ -203,6 +212,15 @@ export default class Heap {
     return foundItemIndices;
   }
 
+  /**
+   *
+   * @param {number} index
+   * @return {*}
+   */
+  getElementAtIndex(index) {
+    return this.heapContainer[index];
+  }
+
   /**
    * @return {boolean}
    */
diff --git a/src/data-structures/priority-queue/PriorityQueue.js b/src/data-structures/priority-queue/PriorityQueue.js
index 1edd5875b4..d1ddc36b63 100644
--- a/src/data-structures/priority-queue/PriorityQueue.js
+++ b/src/data-structures/priority-queue/PriorityQueue.js
@@ -50,8 +50,22 @@ export default class PriorityQueue extends MinHeap {
    */
   changePriority(item, priority, maybeComparator) {
     const comparator = this.getValueComparator(maybeComparator);
-    this.remove(item, comparator);
-    this.add(item, priority);
+    const numberOfItemsToRemove = this.find(item, comparator).length;
+    const itemsToUpdate = [];
+
+    for (let iteration = 0; iteration < numberOfItemsToRemove; iteration += 1) {
+      // We need to find item index to remove each time after removal since
+      // indices are being changed after each heapify process.
+      const indexToRemove = this.find(item, comparator).pop();
+      const itemToUpdate = this.getElementAtIndex(indexToRemove);
+      itemsToUpdate.push(itemToUpdate);
+      this.priorities.delete(itemToUpdate);
+      this.removeIndex(indexToRemove);
+    }
+
+    itemsToUpdate.forEach((itemToUpdate) => {
+      this.add(itemToUpdate, priority);
+    });
 
     return this;
   }
diff --git a/src/data-structures/priority-queue/__test__/PriorityQueue.test.js b/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
index ff78771fd1..4772f25521 100644
--- a/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
+++ b/src/data-structures/priority-queue/__test__/PriorityQueue.test.js
@@ -94,6 +94,35 @@ describe('PriorityQueue', () => {
     expect(priorityQueue.poll()).toBe(JOB1);
   });
 
+  it('should be possible to change the priority of a group of elements', () => {
+    const A = 'a';
+    const B = 'b';
+    const jobA1 = { type: A, id: 1 };
+    const jobB1 = { type: B, id: 2 };
+    const jobB2 = { type: B, id: 3 };
+
+    const priorityQueue = new PriorityQueue();
+
+    priorityQueue.add(jobA1, 2);
+    priorityQueue.add(jobB1, 8);
+    priorityQueue.add(jobB2, 9);
+
+    expect(priorityQueue.peek()).toBe(jobA1);
+
+    const compareByType = (a, b) => {
+      if (a.type === b.type) {
+        return 0;
+      }
+
+      return a.type < b.type ? -1 : 1;
+    };
+
+    priorityQueue.changePriority({ type: B }, 1, compareByType);
+
+    expect(priorityQueue.poll().type).toBe(B);
+    expect(priorityQueue.poll().type).toBe(B);
+  });
+
   it('should be possible to search in priority queue by value', () => {
     const priorityQueue = new PriorityQueue();
 

From a4d7156a0eb6b729f70518d7677f786af23b593d Mon Sep 17 00:00:00 2001
From: appleJax <appleJax@users.noreply.github.com>
Date: Fri, 31 Aug 2018 15:37:44 -0500
Subject: [PATCH 3/3] use JSDoc optional parameter syntax

---
 .../priority-queue/PriorityQueue.js              | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/data-structures/priority-queue/PriorityQueue.js b/src/data-structures/priority-queue/PriorityQueue.js
index d1ddc36b63..032273ba62 100644
--- a/src/data-structures/priority-queue/PriorityQueue.js
+++ b/src/data-structures/priority-queue/PriorityQueue.js
@@ -6,7 +6,7 @@ import Comparator from '../../utils/comparator/Comparator';
 export default class PriorityQueue extends MinHeap {
   /**
    * @constructor
-   * @param {function|undefined} compareValueFunction
+   * @param {function} [compareValueFunction]
    */
   constructor(compareValueFunction) {
     super();
@@ -19,7 +19,7 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
-   * @param {number} priority
+   * @param {number} [priority = 0]
    * @return {PriorityQueue}
    */
   add(item, priority = 0) {
@@ -31,7 +31,7 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
-   * @param {Comparator|function|undefined} maybeComparator
+   * @param {Comparator|function} [maybeComparator]
    * @return {PriorityQueue}
    */
   remove(item, maybeComparator) {
@@ -45,7 +45,7 @@ export default class PriorityQueue extends MinHeap {
   /**
    * @param {*} item
    * @param {number} priority
-   * @param {Comparator|function|undefined} maybeComparator
+   * @param {Comparator|function} [maybeComparator]
    * @return {PriorityQueue}
    */
   changePriority(item, priority, maybeComparator) {
@@ -72,8 +72,8 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
-   * @param {Comparator|function|undefined} maybeComparator
-   * @return {Number[]}
+   * @param {Comparator|function} [maybeComparator]
+   * @return {*[]}
    */
   findByValue(item, maybeComparator) {
     const comparator = this.getValueComparator(maybeComparator);
@@ -82,7 +82,7 @@ export default class PriorityQueue extends MinHeap {
 
   /**
    * @param {*} item
-   * @param {Comparator|function|undefined} maybeComparator
+   * @param {Comparator|function} [maybeComparator]
    * @return {boolean}
    */
   hasValue(item, maybeComparator) {
@@ -91,7 +91,7 @@ export default class PriorityQueue extends MinHeap {
   }
 
   /**
-   * @param {Comparator|function|undefined} maybeComparator
+   * @param {Comparator|function} [maybeComparator]
    * @return {Comparator}
    */
   getValueComparator(maybeComparator) {