raptorjuice

0.2.12 • Public • Published

RaptorJuice

Build Status

Preface

RaptorJuice originated as a utility library for Elixir. It implements iterators, type checking and other generally missed utilities in JavaScript. It is lightweight, (and may become lighter in the future) at around 6.5k uglified.

Purpose

The main goal for RaptorJuice is to implement iterators, or generator objects, with a concise and robust API.

Installation

  • Ensure that you have Node.js on your system.

RaptorJuice is entirely self contained and published on npm so all you need to do is:

npm install raptorjuice

Development

  • Ensure that you have Node.js on your system.

  • Prepare your environment by executing the following commands:

    git clone https://github.com/junglefresh/RaptorJuice.js raptorjuice
    cd raptorjuice
    npm install
    sudo npm install -g grunt-cli mocha
    

Testing

To automatically run tests upon saving changes to the source or tests run the following:

mocha --reporter spec ./spec/**/*.js ./spec/*.js
  • Upon execution of the following command, after each write:

    • The unit tests will be confirmed.

Building

To begin automatically building the project, run:

grunt watch
  • Please note that a Java Runtime is required due to the jsdoc dependency.

  • By executing the following command, upon each write:

    • The unit tests will be confirmed.
    • Documentation will be regenerated if all tests pass.

Demonstration

If you are interested in any of RaptorJuice's secondary functionality, please refer to the documentation. As stated above, RaptorJuice's primary goal is to create generator objects, and more specifically, to greatly simplify asynchronous iteration.

Forward Iteration

An iterator object's default direction is forward.

var gen = R.iterate([ 1, 2, 3 ]);

while(gen.remaining()) {
  console.log(gen.next());
}

As you probably expect, this prints the values: 1, 2, and 3.

Backward Iteration

Iterators can also be reversed.

var gen = R.iterate([ 1, 2, 3 ]).reversed();

while(gen.remaining()) {
  console.log(gen.next());
}

Note thate the reversed method actually makes a new iterator.

This prints the values in the array in reversed order.

Previous Values and Reverse

An example of iterator reversal follows.

var gen = R.iterate([ 1, 2, 3, 4 ]);

gen.next(); // 1
gen.next(); // 2

gen.reverse().next(); // 2
gen.reverse({ discard : true }).next(); // 3

As a convenience, a prev method also exists. It calls the reverse method, then the next method, followed by reverse again. It takes no parameters.

var gen = R.iterate([ 1, 2, 3, 4 ]);

gen.next(); // 1
gen.next(); // 2

gen.prev(); // 2
gen.next(); // 2

gen.next(); // 3

Remaining and Length Counts

The remaining count is direction dependent.

var gen = R.iterate([ 1, 2, 3 ]);

gen.next();
gen.remaining(); // 2

gen.reverse();
gen.remaining(); // 1

gen.reverse();
gen.remaining(); // 2

This is in contrast to the length method, which always returns the actual length of the underlying data model.

var gen = R.iterate([ 1, 2, 3 ]);

gen.next();
gen.length(); // 3

gen.reverse();
gen.length(); // 3

gen.reverse();
gen.length(); // 3

The length is modified automatically by the push and pop methods.

Copyable Iterators

Iterator objects can be copied at any time. The copy will have the same state as the original.

var gen = R.iterate([ 1, 2, 3 ]);

gen.next(); // 1

var cpy = gen.copy();

cpy.next(); // 2
gen.next(); // 2

Each copy will have it's own model, so modifications to one will not affect the other.

Push and Pop

The push and pop methods allow us to modify the modet itself are indexed absolutely. Indexes are 1 based, as opposed to the standard 0 based indicies we are used to.

This was done for mathematical simplicity.

var gen = R.iterate([ 1, 2, 3 ]);

gen.push(1, [ 4 ]);
gen.data(); // [ 4, 1, 2, 3 ];

gen.next(); // 4
gen.pop(1); // [ 4 ]

Iterators can be created empty, then populated later.

var gen = R.iterate();

gen.push([ 1 ]);
gen.push([ 2, 3 ]);

gen.data(); // [ 1, 2, 3 ]

The pop method also may also be supplied with a count parameter to specify how many elements to remove.

var gen = R.iterate([ 1, 2, 3, 4 ]);

gen.next(); // 1
gen.next(); // 2

gen.pop(1, 2); // [ 1, 2 ]

gen.prev(); // undefined
gen.next(); // 3
gen.prev(); // 3

Data and Index

The underlying model and cursor index are available through the data and index methods.

var gen = R.iterate([ 1, 2, 3, 4 ]);

gen.data(); // [ 1, 2, 3, 4 ]

gen.next(); // 1
gen.next(); // 2

gen.index(); // 2

Transform and Generate

To generate the Fibonacci Sequence we could do something like the following.

var fib = R.iterate([ 0, 1 ]);

fib.transform(function(v, u) {
  this.push([ v + u(1) ]);
  return v;
});

fib.next(); // 0
fib.next(); // 1
fib.next(); // 1
fib.next(); // 2
fib.next(); // 3
fib.next(); // 5, and so on...

This allows us to quite concisely calculate the set of Fibonacci numbers up to the first one over 10,000.

var fib = R.iterate([ 0, 1 ]).transform(function(v, u) {
  this.push([ v + u(1) ]);
  return v;
});

while(fib.cursor(true) < 10000) {
  R.log(fib.next());
};

Passing a truthy value to the cursor method ensures that it returns a value, meaning it will be bumped off either end. This lets us initiate a while loop more simply.

Our previous implementation isn't very robust. What would happen if we were to traverse our iterator in reverse? We would be appending values to the end of our iterator and this is not what we want.

var fib = R.iterate([ 0, 1 ]);

fib.transform(function(v, u) {
  if( (this.direction() === 'forward') && !u(2) && u(1) ) {
    this.push([ v + u(1) ]);
  }
  return v;
});

This second version of our Fibonacci generator works as expected. We generate values when traversing forward, and only when we need them.

fib.next(); // 0
fib.next(); // 1
fib.next(); // 1
fib.next(); // 2
fib.next(); // 3

There is only one catch. Since we generate our values with the transform method, we always have more elements in the array than we actually need. In most cases, this shouldn't be a problem, but what if this is unacceptable for your implementation? You need the generate method, which is called when attempting to move off either end of the model. The value returned by the generate method will be added to the model before actually moving allowing us to create our values on the fly.

var fib = R.iterate([ 0, 1 ]);

fib.generate(function(u) {
  var n = u(-2) + u(-1);
  if(!isNaN(n)) { return n; }
});

Now we have a Fibonacci sequence generator from zero to infinity. Not too shabby, but it gets slightly better. As you'd probably expect, the tranform method will still be called on generated values.

fib.transform(function(v, u) {
  return Math.pow(2, v);
});

We can now generate and caches the Fibbonacci sequence only when we need to. When our generator returns a value from its next or prev functions, those values will be returned as powers of two.

fib.next(); //   1
fib.next(); //   2
fib.next(); //   2
fib.next(); //   4
fib.next(); //   8
fib.next(); //  32
fib.next(); // 256

At any point the generate and transform methods can be unset by providing a falsy value to either method.

Chaining Iterators

We can acomplish some pretty interesting things by chaining iterators.

var a = R.iterate([ 1, 2, 3 ]);
var b = R.iterate([ 4, 5, 6 ]);
var c = R.iterate([ 7, 8, 9 ]);

var chain = R.chain(a, b, c);

while(chain.remaining()) {
  R.log(chain.next());
});

Again, it's slightly better than just that. Chains actually call their component iterator's next method. This means their remaining count is consistent even when being incremented as part of a chain.

Reversal of iterators that are part of a chain is not yet handled.

var a = R.iterate([ 1, 2, 3 ]);
var b = R.iterate([ 4, 5, 6 ]);
var c = R.iterate([ 7, 8, 9 ]);

var chain = R.chain(a, b, c);

chain.next(); // 1
chain.next(); // 2

a.remaining(); // 1
b.remaining(); // 3

chain.next(); // 3
chain.next(); // 4

a.remaining(); // 0
b.remaining(); // 2

Conclusion

By using RaptorJuice's iteration techniques, we can flatten, and iterate over a presently indeterminate sequence of values.

When all is said and done, RaptorJuice implements more of a dequeue than an iterator, strictly speaking.

/raptorjuice/

    Package Sidebar

    Install

    npm i raptorjuice

    Weekly Downloads

    30

    Version

    0.2.12

    License

    ISC

    Last publish

    Collaborators

    • psev