Code Monkey home page Code Monkey logo

fastpriorityqueue.js's Introduction

FastPriorityQueue.js : a fast, heap-based priority queue in JavaScript

In a priority queue, you can...

  • query or remove (poll) the smallest element quickly
  • insert elements quickly

In practice, "quickly" often means in logarithmic time (O(log n)).

A heap can be used to implement a priority queue.

FastPriorityQueue is an attempt to implement a performance-oriented priority queue in JavaScript. It can be several times faster than other similar libraries. It is ideal when performance matters.

License: Apache License 2.0

Usage

var x = new FastPriorityQueue();
x.add(1);
x.add(0);
x.add(5);
x.add(4);
x.add(3);
x.peek(); // should return 0, leaves x unchanged
x.size; // should return 5, leaves x unchanged
while (!x.isEmpty()) {
  console.log(x.poll());
} // will print 0 1 3 4 5
x.trim(); // (optional) optimizes memory usage

You can also provide the constructor with a comparator function.

var x = new FastPriorityQueue(function(a, b) {
  return a > b;
});
x.add(1);
x.add(0);
x.add(5);
x.add(4);
x.add(3);
while (!x.isEmpty()) {
  console.log(x.poll());
} // will print 5 4 3 1 0

If you are using node.js, you need to import the module:

var FastPriorityQueue = require('fastpriorityqueue');
var b = new FastPriorityQueue(); // initially empty
b.add(1); // add the value "1"

Instance methods summary:

  • add(value): add an element into the queue; runs in O(log n) time.
  • poll(): remove and return the element on top of the heap (smallest element); runs in O(log n) time. If the priority queue is empty, the function returns undefined.
  • remove(value): remove an element matching the provided value, if found, from the queue. The item is matched by using the queue's comparator. Returns true if the element is removed, false otherwise.
  • removeOne(callback): execute the callback function for each item of the queue and remove the first item for which the callback will return true. Returns the removed item, or undefined if nothing is removed. The callback must be a pure function.
  • removeMany(callback[, limit]): execute the callback function for each item of the queue and remove each item for which the callback will return true, up to a max limit of removed items if specified or no limit if unspecified. Returns an array containing the removed items. The callback must be a pure function.
  • replaceTop(value): poll() and add(value) in one operation. This is useful for fast, top-k queries. Returns the removed element or undefined, similar to poll().
  • heapify(array): replace the content of the heap with the provided array, then order it based on the comparator.
  • peek(): return the top of the queue (smallest element) without removal, or undefined if the queue is empty; runs in O(1) time.
  • isEmpty(): return true if the the queue has no elements, false otherwise.
  • clone(): copy the priority queue into another, and return it. Queue items are shallow-copied. Runs in O(n) time.
  • forEach(callback): iterate over all items in the priority queue from smallest to largest. callback should be a function that accepts two arguments, value (the item), and index, the zero-based index of the item.
  • trim(): clean up unused memory in the heap; useful after high-churn operations like many add()s then remove()s.

npm install

  $ npm install fastpriorityqueue

Computational complexity

The function calls "add" and "poll" have logarithmic complexity with respect to the size of the data structure (attribute size). Looking at the top value is a constant time operation.

Testing

Using node.js (npm), you can test the code as follows...

  $ npm install mocha
  $ npm test

Is it faster?

It tends to fare well against the competition. In some tests, it can be five times faster than any other JavaScript implementation we could find.

$ node benchmark/test.js
Platform: darwin 20.1.0 x64
Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
Node version 14.7.0, v8 version 8.4.371.19-node.12

Comparing against:
js-priority-queue: https://github.com/adamhooper/js-priority-queue 0.1.5
stablepriorityqueue: https://github.com/lemire/StablePriorityQueue.js 0.1.2
heap.js: https://github.com/qiao/heap.js 0.2.6
binaryheapx: https://github.com/xudafeng/BinaryHeap 0.1.1
priority_queue: https://github.com/agnat/js_priority_queue 0.1.3
js-heap: https://github.com/thauburger/js-heap 0.3.1
queue-priority: https://github.com/augustohp/Priority-Queue-NodeJS 1.0.0
priorityqueuejs: https://github.com/janogonzalez/priorityqueuejs 2.0.0
qheap: https://github.com/andrasq/node-qheap 1.4.0
yabh: https://github.com/jmdobry/yabh 1.2.0

starting dynamic queue/enqueue benchmark
FastPriorityQueue x 36,816 ops/sec ±0.74% (92 runs sampled)
FastPriorityQueue---replaceTop x 107,942 ops/sec ±0.71% (91 runs sampled)
sort x 6,240 ops/sec ±1.65% (92 runs sampled)
StablePriorityQueue x 10,333 ops/sec ±4.09% (91 runs sampled)
js-priority-queue x 14,435 ops/sec ±1.97% (91 runs sampled)
heap.js x 6,568 ops/sec ±2.29% (90 runs sampled)
binaryheapx x 8,595 ops/sec ±0.56% (94 runs sampled)
priority_queue x 8,201 ops/sec ±0.74% (94 runs sampled)
js-heap x 557 ops/sec ±1.70% (89 runs sampled)
queue-priority x 291 ops/sec ±2.46% (88 runs sampled)
priorityqueuejs x 13,864 ops/sec ±2.02% (90 runs sampled)
qheap x 26,882 ops/sec ±1.81% (93 runs sampled)
yabh x 10,472 ops/sec ±1.50% (93 runs sampled)
Fastest is FastPriorityQueue

Benchmarks on an Apple M1:

Platform: darwin 20.2.0 arm64
Apple M1
Node version 15.6.0, v8 version 8.6.395.17-node.23

Comparing against:
js-priority-queue: https://github.com/adamhooper/js-priority-queue 0.1.5
stablepriorityqueue: https://github.com/lemire/StablePriorityQueue.js 0.1.2
heap.js: https://github.com/qiao/heap.js 0.2.6
binaryheapx: https://github.com/xudafeng/BinaryHeap 0.1.1
priority_queue: https://github.com/agnat/js_priority_queue 0.1.3
js-heap: https://github.com/thauburger/js-heap 0.3.1
queue-priority: https://github.com/augustohp/Priority-Queue-NodeJS 1.0.0
priorityqueuejs: https://github.com/janogonzalez/priorityqueuejs 2.0.0
qheap: https://github.com/andrasq/node-qheap 1.4.0
yabh: https://github.com/jmdobry/yabh 1.2.0

starting dynamic queue/enqueue benchmark
FastPriorityQueue x 47,894 ops/sec ±0.19% (100 runs sampled)
FastPriorityQueue---replaceTop x 187,809 ops/sec ±0.09% (97 runs sampled)
sort x 9,285 ops/sec ±0.10% (100 runs sampled)
StablePriorityQueue x 19,830 ops/sec ±0.49% (97 runs sampled)
js-priority-queue x 28,382 ops/sec ±0.10% (98 runs sampled)
heap.js x 5,504 ops/sec ±0.22% (100 runs sampled)
binaryheapx x 10,473 ops/sec ±0.11% (98 runs sampled)
priority_queue x 9,041 ops/sec ±0.33% (97 runs sampled)
js-heap x 390 ops/sec ±0.04% (96 runs sampled)
queue-priority x 438 ops/sec ±0.09% (95 runs sampled)
priorityqueuejs x 14,797 ops/sec ±0.07% (101 runs sampled)
qheap x 38,108 ops/sec ±0.12% (99 runs sampled)
yabh x 14,942 ops/sec ±0.24% (99 runs sampled)

Note that qheap has been updated following the introduction of FastPriorityQueue, with a reference to FastPriorityQueue which might explains the fact that its performance is comparable to FastPriorityQueue.

Insertion order

A binary heap does not keep track of the insertion order.

You might also like...

If you like this library, you might also like

fastpriorityqueue.js's People

Contributors

amorites avatar barbarrosa avatar blaskovicz avatar dependabot[bot] avatar erikbrinkman avatar iradul avatar jarred-sumner avatar lemire avatar mafintosh avatar restuta avatar ruiaraujo avatar ryokun6 avatar sangaman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastpriorityqueue.js's Issues

Wrong sort result.

Reproduce

  1. let queue_s = new FastPriorityQueue((a, b)=>{
    return a.priority <= b.priority;
    });
  2. add 50 items in a queue, priority from 0~10
  3. queue.forEach((value, index)=>{ if(value.priority == 3) value.priority = 0 }); // Change priority.
  4. while (!queue_s.isEmpty()) {
    console.log(queue_s.poll());
    }
  5. log like, wrong sort by priority
{ priority: 1, number: 22, serialNumber: 'Serial 22' }
{ priority: 1, number: 32, serialNumber: 'Serial 32' }
{ priority: 1, number: 38, serialNumber: 'Serial 38' }
{ priority: 0, number: 34, serialNumber: 'Serial 34' }
{ priority: 1, number: 42, serialNumber: 'Serial 42' }
{ priority: 0, number: 41, serialNumber: 'Serial 41' }
{ priority: 1, number: 25, serialNumber: 'Serial 25' }
{ priority: 2, number: 1, serialNumber: 'Serial 1' }
{ priority: 2, number: 45, serialNumber: 'Serial 45' }

But when I change
let queue_s = new FastPriorityQueue((a, b)=>{
return a.priority > b.priority;
});
All is OK.

The difference is change comparator from > to <=
Or should I copy values to another array and heapify again?

Removing any value from the heap

This is more of a suggestion / question for an enhancement than a bug or issue.

In short: expand poll and peek to values at indices other than 0.

In long: I am using the queue to keep track of properties on a set of elements. In my case, the set of elements can be split into two at some point, in which case I am creating a new set and update the old set (i.e., remove elements now being part of the new set). I would like to avoid having to re-heapify the elements on the old set so I expanded the poll function to accept an integer such that any value on the heap can be removed. Same addition for the peek.

The changes are minimal:

FastPriorityQueue.prototype.peek = function (i) {
    i = i | 0;
    if (this.size == 0 || i >= this.size) return undefined;
    return this.array[i];
};

FastPriorityQueue.prototype.poll = function (i) {
    i = i | 0;
    if (this.size == 0 || i >= this.size) return undefined;
    var ans = this.array[i];
    if (this.size > 1) {
        this.array[i] = this.array[--this.size];
        this._percolateDown(i);
    } else {
        this.size -= 1;
    }
    return ans;
};

(peek(i) is not expected to return the i-th element in terms of the priority but merely the i-th element on the array.)

I am wondering if this addition could be useful in general, hence, worth a pull request or if you want to keep things simple?

I've set up a fork and added a unit test in case you are interested: https://github.com/flekschas/FastPriorityQueue.js

I don't think `minimist` is required

A quick read of the source code and I reached the conclusion that minimist isn't required for this.

Can someone explain why it is included in package.json?

The engine "node" is incompatible with this module. Expected version "~0.4".

I run the following command

yarn add fastpriorityqueue

error [email protected]: The engine "node" is incompatible with this module. Expected version "~0.4".
error Found incompatible module
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.

I see that in https://github.com/lemire/FastPriorityQueue.js/blob/master/package.json#L26-L35 "queue-priority": "^1.0.0" is dependency but it is not really dependency it is used only in benchmark

Can you please remove all from dependencies and add it to benchmark folder only ?

Should this library be rewritten in TS

Thanks for maintaining a wonderful library

I copied a lot of code from here and implemented a priority queue for a new memory cache library I'm making

K smallest elements

Thanks for an awesome implementation that outshines its competitors.

Would it be possible to add an O(k) or O(klogk) algorithm for finding the k smallest elements of the queue?

It can be done in O(klogn) time right now by polling k times and then adding them all back.

An O(k) or O(klogk) algorithm is possible, however, as described here: https://stackoverflow.com/questions/47892037/finding-k-smallest-elements-in-a-min-heap-worst-case-complexity

Do you believe it would possible to implement? I'm likely gonna take a crack at it myself.

Problem with npm dependency in published package

The latest publish to npm has a dependency on the npm package itself, which is causing our travis builds to fail. This is peculiar, because npm is not listed as a dependency anywhere in package.json or package-lock.json. Could you make sure that you don't have npm installed locally in the FastPriorityQueue.js folder and try publishing again, 0.6.1 I guess?

See here: https://www.npmjs.com/package/fastpriorityqueue. Previous versions did not have the npm dependency, and I don't think it should've been introduced in #16 because the dependencies in package.json did not change and it published correctly to here: https://www.npmjs.com/package/@exchangeunion/fastpriorityqueue.

Thanks.

element is duplicated when remove or removeOne is called

I have a map of Queues and When I want to remove an element from a given queue the element itself is removed but the one below it got duplicated.

let mapOfQueues=new Map(); //a map of queues

let queue= new FastPriorityQueue(function(a, b) {
            return parseInt(a.priorite) < parseInt(b.priorite);
           });

mapOfQueues.set(key, queue);//add queue to map
let queueTarget = mapOfQueues.get(key);//retrieve queue from map 
var callback = function(val) {
return val.id==id;
}
let removedItem = queued.removeOne(callback);

elements are json object like
{
id: 130,
data: 'data',
priority: '20',
}

example

Queue is

{
  id: 130,
  data: 'data',
  priority: '20',
},
{
  id: 131,
  data: 'data',
  priority: '30',
},
{
  id: 132,
  data: 'data',
  priority: '40',
}

if element with id 131 is removed the queue becomes

{
  id: 130,
  data: 'data',
  priority: '20',
},
{
  id: 132,
  data: 'data',
  priority: '40',
},
{
  id: 132,
  data: 'data',
  priority: '40',
}

Iterate

How can I iterate over queue? I have a scenario when I lost connection I must iterate over queue and remove some items (Not all)

Peek returns data when size is 0

var FastPriorityQueue = require('fastpriorityqueue');
var q = new FastPriorityQueue();
q.add(1);
q.poll(); // 1
q.size; // 0
q.peek(); // Should be undefined, but is 1

Revisit performance testing

My benchmark on merging K number of sorted arrays yields slightly different result and qheap is faster. Let's maybe construct a set of meaningful tests in a separate repo so we can re-produce results and lock down versions of the libraries?

What library version did you use in your tests?

I noticed that you use different comparator functions, is there reason for it? Like for other libs you use a - b comparators and subtraction can be significantly slower than a < bthat you use for others. E.g. qheap supports this by default with comparBefore, but who knows maybe it's not a default for other libs. (e.g. js-priority-queue uses 3 way comparator and it seem to be the only option)

Also it seems https://github.com/andrasq/node-qheap has a reference to this very repo https://github.com/andrasq/node-qheap that shows that it's the fastest and claims it used to show it. What is going on? =)

When I updated comparators to be the same one, my local test also yielded similar result:

❯ node test.js
Platform: darwin 15.3.0 x64
Intel(R) Core(TM) i7-4850HQ CPU @ 2.30GHz
Node version 7.3.0, v8 version 5.4.500.45

Comparing against:
js-priority-queue: https://github.com/adamhooper/js-priority-queue
heap.js: https://github.com/qiao/heap.js
binaryheapx: https://github.com/xudafeng/BinaryHeap
priority_queue: https://github.com/agnat/js_priority_queue
js-heap: https://github.com/thauburger/js-heap
queue-priority: https://github.com/augustohp/Priority-Queue-NodeJS
priorityqueuejs: https://github.com/janogonzalez/priorityqueuejs
qheap: https://github.com/andrasq/node-qheap
yabh: https://github.com/jmdobry/yabh

starting dynamic queue/enqueue benchmark
FastPriorityQueue x 25,991 ops/sec ±1.90% (82 runs sampled)
js-priority-queue x 6,891 ops/sec ±1.50% (84 runs sampled)
heap.js x 2,428 ops/sec ±1.15% (86 runs sampled)
binaryheapx x 3,592 ops/sec ±1.04% (85 runs sampled)
priority_queue x 3,181 ops/sec ±1.37% (85 runs sampled)
js-heap x 236 ops/sec ±1.38% (78 runs sampled)
queue-priority x 366 ops/sec ±1.24% (84 runs sampled)
priorityqueuejs x 5,880 ops/sec ±0.93% (66 runs sampled)
qheap x 31,253 ops/sec ±1.23% (87 runs sampled)
yabh x 3,658 ops/sec ±1.14% (89 runs sampled)
Fastest is qheap

(btw another issue that test fails saying "pluck" doesn't exist, this is used to output last line)

is it possible to delay events?

Is it possible to use this lib to delay events?

We need a tool to schedule tasks for some time in the future. Is this library feet for such purposes?

'removeMany' with a stop/continue function

Currently removeMany accepts a limit, which allows the iteration to stop based on number of items.

However, when we need to remove items that fall within a range, the no. of items to visit/remove are not known before hand. Request to add a shouldContinue kind of callback to the removeMany that allows the user to control if the removal is done or should proceed to next item.

This is useful to remove a continuous sequence of values in the queue.

For example, when input data contains duplicates, just doing poll once will not be sufficient. One has to remove all elements below the next higher value.

Or, when one wants to remove all values below certain limit x, one needs to keep removing items and stop once a value higher than x is encountered.

One can do it with repeat calls to poll, but since removeMany already exists, it would be great if a continuation control method can be added to it, that gives user more dynamic control on when to stop the iteration.

"removeMany" fails to iterate over similarly weighted values

Given a tree with the following priority values:

    1
  /   \
 1     2
/ \   / \
7 4   2 3

If I use traversal state or embedded data (rather than the priority itself) to determine whether I want to remove an object, then relying on removeMany for that operation may fail to check some items for removal, particularly when some items share the same priority.

Example, using traversal state:

var FastPriorityQueue = require("./FastPriorityQueue.js");

var saw2 = false;
var removed2 = false;

var fpq = new FastPriorityQueue();

[1,1,2,7,4,2,3].forEach(e => fpq.add(e));

fpq.removeMany(v => {
  console.log(v); // prints numbers in following order - 1,1,2,7,4,2,1,7,4,2
  if (v === 3) return true;
  if (removed2 || v !== 2) return false;
  if (!saw2) {
    saw2 = true;
    return false;
  }
  removed2 = true;
  return true;
});
fpq.trim();

fpq.array; // [ 1, 3, 1, 7, 4, 2 ]

The above case never checked for whether we should remove 3, so it remains in the priority queue.

kSmallest() not equivalent to calling poll() repeatedly

The kSmallest function has a comment saying:

// return the k 'smallest' elements of the queue
// runs in O(k log k) time
// this is the equivalent of repeatedly calling poll, but
// it has a better computational complexity, which can be
// important for large data sets.

But it's not equivalent, because poll() removes the polled element and this function not. Example:

const FastPriorityQueue = require('fastpriorityqueue')

const q = new FastPriorityQueue()

q.add(1)
q.add(2)
q.add(3)

console.log(q.size)
// >> Outputs 3

q.kSmallest(2)

console.log(q.size)
// >> Outputs 3

for (let i = 0; i < 2; i++) {
  q.poll()
}

console.log(q.size)
// >> Outputs 1

Is this behavior expected? Either the comment or the documentation should be changed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.