1.1.0 • Published 9 years ago

fluent-iterators v1.1.0

Weekly downloads
6
License
-
Repository
github
Last release
9 years ago

Fluent Iterators

This is a Guava-esque library of javascript iterators for transforming, memoizing, grouping, sorted-merging, sliding-window-reducing, and doing more stuff to your iterables. This is a CommonJS package compatible with all AMD loaders, like RequireJS, available via Bower, and soon NPM, and it works in a browser or in Node.JS.

Installation

Bower

Add this line to your bower.json file. It provides the FluentIterators global, and adds the syntactic sugar Array.iterator().

 "dependencies": {
 ...
  "fluent-iterators": "~1.1.0"
 ...
 }

Examples

This library provides all these fluent functions for building transformative iterators. You can chain together a pipeline of iterators that transform a stream of objects as they're pulled through.

['a', 'b', 'c'].iterator().transform(...).group(...).limit(...).filter(...)
  .memoize().window(...).mergeSortedIterators([...]).aggregate(...).toArray() and .forEach(callback);

In this framework, an "iterator" is any object with a next() function, like this one.

var c = 0;
var myCounterIterator = {
  next: function () {
    return c++;
  }
};

There's no hasNext() function, so iterators indicate end-of-stream by returning null or undefined from the next() function. For example, this iterator terminates after 10 iterations.

var c = 0;
var myCounterIterator = {
  next: function () {
    return c < 10 ? c++ : null;
  }
};

You can import this package via CommonJS, and either instantiate exported classes, or use the fluent syntax. You will need to wrap your hand made iterator objects to use the fluent syntax with them, using the asIterator(iterator) utility function available in the CommonJS module exports, or on the FluentIterators global variable, in the standalone bundle.

// in CommonJS
var iterators = require('fluent-iterators');

iterators.asIterator(myCounterIterator).group(...)
  .memoize().window(...).mergeSortedIterators([...]).toArray();
// in standalone mode, with the FluentIterators global variable.
FluentIterators.asIterator(myCounterIterator).group(...)
  .memoize().window(...).mergeSortedIterators([...]).toArray();

You can get a new iterator from an array, with fluent syntax.

var iterators = require('fluent-iterators');

var it = [1, 2].iterator();

it.next(); // 1
it.next(); // 2
it.next(); // null

Or by instantiating the exported class.

var iterators = require('fluent-iterators');

var it = new iterators.ArrayIterator([1, 2]);

it.next(); // 1
it.next(); // 2
it.next(); // null

Or with the factory function toIterator(), with either an array or an object. You can iterate over the key value pairs in any javascript object, with toIterator().

var iterators = require('fluent-iterators');

var it = iterators.toIterator([1, 2]);

it.next(); // 1
it.next(); // 2
it.next(); // null

var objIt = iterators.toIterator({foo: bar});

objIt.next(); // {key: 'foo', value: 'bar'}
objIt.next(); // null

Merge Sorted Iterators

You can merge sorted iterators into a single, sorted iterator. By default, it will use the items' natural ascending order. If your input iterators are out of order, then the merged iterator will also return items out of order.

var s1 = [1, 5, 5, 100, 101].iterator();
var s2 = [0, 10, 20, 30, 40].iterator();
var s3 = [9, 10, 11].iterator();

var merged = s1.mergeSortedIterators([s2, s3]);

merged.next(); // 0
merged.next(); // 1
merged.next(); // 5
merged.next(); // 5
merged.next(); // 9
merged.next(); // 10
merged.next(); // 10
merged.next(); // 11
merged.next(); // 20
merged.next(); // 30
merged.next(); // 40
merged.next(); // 100
merged.next(); // 101
merged.next(); // null

Or you can provide your own comparator.

var s1 = [{x:1}, {x:5}, {x:5}, {x:100}, {x:101}].iterator();
var s2 = [{x:0}, {x:10}, {x:20}, {x:30}, {x:40}].iterator();
var s3 = [{x:9}, {x:10}, {x:11}].iterator();

var asc = function (lhs, rhs) {
  return lhs.x - rhs.x;
};

var merged = s1.mergeSortedIterators([s2, s3], asc);

merged.next(); // {x:0}
merged.next(); // {x:1}
merged.next(); // {x:5}
merged.next(); // {x:5}
merged.next(); // {x:9}
merged.next(); // {x:10}
merged.next(); // {x:10}
merged.next(); // {x:11}
merged.next(); // {x:20}
merged.next(); // {x:30}
merged.next(); // {x:40}
merged.next(); // {x:100}
merged.next(); // {x:101}
merged.next(); // null

Or you can use the new keyword, with the exported class.

var iterators = require('fluent-iterators');

var s1 = [{x:1}, {x:5}, {x:5}, {x:100}, {x:101}].iterator();
var s2 = [{x:0}, {x:10}, {x:20}, {x:30}, {x:40}].iterator();
var s3 = [{x:9}, {x:10}, {x:11}].iterator();

var asc = function (lhs, rhs) {
  return lhs.x - rhs.x;
};

var merged = new iterators.SortedIteratorMerger([s1, s2, s3], asc);

merged.next(); // {x:0}
merged.next(); // {x:1}
...

Grouping

You can group together consecutive items from a source iterator.

By default, group() uses strict equality.

    var source = [5, 2, 2, 4, 4, 4, 1, 7, 2, 2].iterator();
    var grouped = new Iterators.GroupingIterator(source);

    grouped.next(); // [5]
    grouped.next(); // [2, 2] (it captures contiguous groups)
    grouped.next(); // [4, 4, 4] (it can capture any length group)
    grouped.next(); // [1]
    grouped.next(); // [7] (it won't group consecutive individuals)
    grouped.next(); // [2, 2] (it doesn't matter that we already had a group of 2's)
    grouped.next(); // null  (it emits a null end-of-stream token)

Or you can supply your own grouping function:

var source = [{a:5, b:1}, {a:2, b:1}, {a:2, b:1}].iterator();
var grouped = source.group(function (lhs, rhs) {
  return lhs.a === rhs.a; // this function doesn't need to handle null end-of-stream tokens
});

grouped.next(); // [{a:5, b:1}]
grouped.next(); // [{a:2, b:1}, {a:2, b:1}]
grouped.next(); // null

Limit

Terminate an iterator after a max number of iterations.

var limited = ['a', 'b', 'c'].iterator().limit(2);

limited.next(); // 'a'
limited.next(); // 'b'
limited.next(); // null

Filter

Filter items out of a source iterator.

var isGreaterThanTen = function (n) {
  return n > 10;
};

var filtered = [1, 11, 2, 12].iterator().filter(isGreaterThanTen);

filtered.next(); // 11
filtered.next(); // 12
filtered.next(); // null

toArray()

Drain an iterator into an array.

var gtTen = function (n) {
  return n > 10;
};

[1, 11, 2, 12].iterator().filter(gtTen).toArray(); // [11, 12]

forEach()

Drain an iterator into a callback function, like you can do with ararys.

var sum = 0;
[1, 2, 3, 4].iterator().forEach(function (item) {
  sum += item;
});
// sum == 10

Transform

You can transform items from a source iterator.

var prependA = function (n) {
  return 'a' + n.toString();
};

var transformed = [1, 11, 2].iterator().transform(prependA);

transformed.next(); // 'a1'
transformed.next(); // 'a11'
transformed.next(); // 'a2'
transformed.next(); // null

Sliding-Window Reducer

You can use a reducer callback function, as per the native Array.reduce() api. You can use this even if the browser doesn't provide a native Array.reduce() function. This does a reduce() scan over the whole window, in each iteration, so this provides O(n * windowSize) time cost.

var source = [8,9,0,5].iterator();

var sumReducer = function(previousValue, currentValue, index, array){
  return previousValue + currentValue;
};

var windowSize = 2;
var reduced = source.window(windowSize, sumReducer);

reduced.next(); // 8
reduced.next(); // 17
reduced.next(); // 9
reduced.next(); // 5
reduced.next(); // null

Or you can use an accumulator object, with add(item), remove(item), and reduce() functions. The window iterator calls add, remove, and reduce, once for each iteration, so you can get O(n) time cost if each of those functions has a O(1) time cost. This example does the sliding window summation in O(n) time.

var source = [8,9,0,5].iterator();

var sum = 0;
var sumAccumulator = {
  add : function (item) {
    sum += item;
  },
  remove : function (item) {
    sum -= item;
  },
  reduce : function () {
    return sum;
  }
};

var reduced = source.window(2, sumAccumulator);

reduced.next(); // 8
reduced.next(); // 17
reduced.next(); // 9
reduced.next(); // 5
reduced.next(); // null

Aggregate

You can aggregate contiguous duplicate items from an iterator. It uses an equality function to decide whether to reduce the next item. The equality function has to handle null inputs, because lhs is always null in the first iteration. Note: This api serves a very specific use case, and I'm working on a better reduce() function that doesn't depend on a concept of item equality.

    var stream = [{x:1}, {x:5}, {x:5}, {x:2}].iterator().aggregate(
      function (lhs, rhs) {
        return {x: lhs.x + rhs.x };
      }, function (lhs, rhs) {
        if (!lhs) {
          return !rhs;
        }
        if (!rhs) {
          return false;
        }
        return lhs.x === rhs.x;
      }
    );


summed.next().x; // 2
summed.next().x; // 10
summed.next().x; // 2
summed.next().x; // 33
summed.next(); // null

Acknowledgements

See this great template for starting a project like this one: browserify-grunt-mocha-template, and see the blog post that goes with it: Cross Platform Javascript.

License

MIT