grunge
Version:
A generator-based sequence generator and utility.
441 lines (394 loc) • 10.7 kB
JavaScript
(function(root, factory){
'use strict';
if (typeof exports === 'object') {
// CommonJS
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
// AMD
define([], function () {
return (root.Grunge = factory());
});
} else {
// Global Variables
root.Grunge = factory();
}
})(this, function(){
'use strict';
// only pass in x when step is a function
// pass in x for memory concerns if you don't always need the whole array.
var Grunge = function(start, step, x){
// protection against using the class without the new keyword
if(!(this instanceof Grunge)){
return new Grunge(start, step, x);
}
//if no arguments are passed, return an empty Object
if(start === undefined){
return this;
}
// if an instance of Grunge is passed in, and a step
if((start instanceof Grunge) && (typeof step === 'number')){
//call .step on the object and return that
return start.step(step);
}
// accepting a 'step' function. Takes a value and returns the next value. gets three values (value, index, arrayOfLast 10 values)
if(typeof step === 'function'){
//x = (x && x > 0) ? x : 10;
this.generator = function* (){
var currValue = start;
var index = 0;
var valuesSoFar = [];
while(true){
yield currValue;
valuesSoFar.push(currValue);
if(x && x > 0 && valuesSoFar.length > x){
valuesSoFar.shift();
}
currValue = step(currValue, index, valuesSoFar);
index ++;
};
};
return this;
}
//if an array is passed in iterate over the elements.
if(Array.isArray(start)){
this.generator = function*(){
for(var i = 0; i < start.length; i++){
yield start[i];
}
};
this.length = start.length;
if(!!step){
return this.step(step);
}
return this;
}
//if a generator function is passed in
if(typeof start === 'function'){
var startIterator = start();
if(Grunge.isGenerator(startIterator)){
this.generator = start;
if(!!step){
return this.step(step);
}
return this;
}
//if a generator like function is passed
if(Grunge.isGeneratorLike(startIterator)){
this.generator = function* (){
try {
var elem = startIterator.next();
while(elem.done === false){
yield elem.value;
elem = startIterator.next();
}
return elem.value;
} catch (e){
return e;
}
};
if(!!step){
return this.step(step);
}
return this;
}
}
//finally accounting for primitive values
this.generator = function* (){
var i = 0, elem = start;
while(true){
yield elem;
//Grunge accepts a step function as well as a value
if(typeof step === 'function'){
elem = step(elem, i);
} else {
elem += step;
}
i++;
}
};
};
Grunge.prototype.skip = function(num){
if(!Grunge.isNaturalNumber(num)){
throw new Error("skip takes a natural number");
}
var that = this;
var newGrunge = new Grunge(function* (){
try {
var startIterator = that.generator();
var elem = startIterator.next();
var i = 0;
while(elem.done === false){
if(i >= num){
yield elem.value;
}
elem = startIterator.next();
i++;
}
if(i > num) {
return elem.value;
} else {
return;
}
} catch (e){
return e;
}
});
if(!!this.length){
newGrunge.length = Math.min(this.length - num, 0);
}
return newGrunge;
};
Grunge.prototype.step = function(num){
if(!num || num === 0){
return this;
}
if(typeof num !== 'number' && typeof num !== 'function'){
throw new Error('step takes a number or step function');
}
var that = this;
var i = 0;
var newGrunge = new Grunge(function* (){
var iterator = that.generator();
while(true){
var elem = iterator.next();
var skipNumber = (typeof num === 'number')? num : num(elem.value, i);
i+= skipNumber;
for(var j = 0; j < skipNumber; j++){
var temp = iterator.next();
if(temp.done){
elem.done = true;
break;
}
}
if(elem.done){
return elem.value;
}
yield elem.value;
}
});
newGrunge.length = this.length;
if(typeof num === 'number'){
newGrunge.length = Math.floor(newGrunge.length/num);
}
return newGrunge;
};
// Grunge.map => takes a map function and returns a new instace of Grunge
// good for chaining
Grunge.prototype.map = function(func){
var that = this;
if(!func){
return this;
}
var newGrunge = new Grunge(function* (){
try {
var startIterator = that.generator();
var i = 0;
var elem = startIterator.next();
while(elem.done === false) {
yield func(elem.value, i);
elem = startIterator.next();
i++;
}
return func(elem.value, i);
} catch (e){
return e;
}
});
newGrunge.length = this.length;
return newGrunge;
};
//takes a truth test. Returns a new instance of Grunge
Grunge.prototype.filter = function(func){
var that = this;
if(!func){
return this;
}
var newGrunge = new Grunge(function* (){
try {
var startIterator = that.generator();
var i = 0;
var elem = startIterator.next();
while(elem.done === false){
if(!!func(elem.value, i)){
yield elem.value;
}
i++;
elem = startIterator.next();
}
if(!!func(elem.value, i)){
return elem.value;
}
} catch (e){
return e;
}
});
newGrunge.length = this.length;
return newGrunge;
};
//Important function. Needed to make the infinite sequence a finite sequence
Grunge.prototype.take = function(num){
var that = this;
if(!num || !Grunge.isNaturalNumber(num)){
throw new Error('Take takes a natural number');
}
if(this.length < num){
return this;
}
var newGrunge = new Grunge(function* (){
var iterator = that.generator();
for(var i = 0; i < num; i++){
var elem = iterator.next();
yield elem.value;
if(elem.done){
break;
}
}
});
newGrunge.length = num;
return newGrunge;
};
Grunge.prototype.merge = function(){
var args = Array.prototype.map.call(arguments, function(arg){
return new Grunge(arg);
});
args.unshift(this);
// args.forEach(function(s){
// console.log(s.toArray());
// });
var seqs = args.map(function(sequence){
return sequence.generator();
});
var bool = true;
var newGrunge = new Grunge(function(){
while(bool){
bool = false;
var arr = seqs.map(function(gen){
var el = gen.next();
//console.log("test:", el);
yield el.value;
if(el.done){
break;
}
});
}
});
newGrunge.length = args.reduce(function(a,b){
if(a === undefined || b ===undefined){
return undefined;
} else {
return Math.max(a,b);
}
}, 0);
return newGrunge;
};
Grunge.prototype.forEach = function(func){
if(!this.length){
throw new Error('Cannot Loop over infinite sequence');
}
if(!func){
return this;
}
// old version with for-of loops
// for(var elem of this.generator()){
// func(elem);
// }
try {
var startIterator = this.generator();
var i = 0;
var elem = startIterator.next();
while(elem.done === false){
func(elem.value, i);
elem = startIterator.next();
i++;
}
// not sure why this is happening
//func(elem.value, i);
} catch (e){
return e;
}
};
Grunge.prototype.forEachAsync = function(func, time){
if(!func){
return this;
}
time = time || 0;
var iterator = this.generator();
var index = 0;
var callNext = function(){
var el = iterator.next();
func(el.value, index);
if(!el.done){
index++;
setTimeout(callNext, time);
}
};
callNext();
};
Grunge.prototype.reduce = function(count, func, startValue){
if(typeof count === 'function'){
if(!this.length){
throw new Error('Cannot Loop over infinite sequence');
}
if(startValue === undefined){
startValue = this.generator().next().value;
}
this.forEach(function(elem){
startValue = func(elem, startValue);
});
return startValue;
} else {
if(!Grunge.isNaturalNumber(count)){
throw new Error('the first argument must be a natural number for this to work');
}
var myStartValue = startValue || this.generator().next().value;
var that = this;
var newGrunge = new Grunge(function* () {
var iterator = that.generator();
//var temp = myStartValue;
var lastValue = {value : myStartValue};
while(true){
var temp = startValue !== undefined ? startValue : lastValue.value;
for(var i = 0; i < count; i++){
lastValue = iterator.next();
temp = func(lastValue.value, temp);
if(lastValue.done){
break;
}
}
yield temp;
if(lastValue.done){
break;
}
}
});
if(this.length) {
newGrunge.length = Math.ceil(this.length/count);
}
return newGrunge;
}
};
Grunge.prototype.toArray = function(){
var result = [];
this.forEach(function (elem, index) {
result.push(elem);
});
return result;
};
Grunge.prototype.first = function(num){
return this.take(num).toArray();
};
/*
* Helper Methods
*/
//Credit : Visionmedia/Co
Grunge.isGenerator = function(obj) {
return obj && 'function' === typeof obj.next && 'function' === typeof obj.throw;
};
Grunge.isGeneratorLike = function(obj) {
return obj && 'function' === typeof obj.next;
};
Grunge.isNaturalNumber = function(num){
return (typeof num === 'number') && num >= 1 && num % 1 === 0;
};
return Grunge;
});