npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

fluent-iterators

v1.1.0

Published

A library of iterators: group, transform, sorted-merge, memoize/replay, sliding-window reduce, etc.

Downloads

6

Readme

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