You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
5.1 KiB
206 lines
5.1 KiB
/**
|
|
* ∞
|
|
* Infinity: Create infinitely generating lists in JavaScript.
|
|
* @version 0.2.0
|
|
* @author Muthu Kumar (MKRhere)
|
|
*/
|
|
|
|
// Utils
|
|
const { always, isNonZeroFalsy, stringify, areNumbers } = require('./utils');
|
|
|
|
/**
|
|
* An item of the InfiniteList class. Created when calling .get(n) on an InfiniteList.
|
|
* Exposed for instanceof utility sake. Not to be called directly.
|
|
* @class InfiniteListItem
|
|
*/
|
|
class InfiniteListItem {
|
|
/**
|
|
* Creates an instance of InfiniteListItem.
|
|
* @param {any} list Parent list, instance of InfiniteList
|
|
* @param {any} value Current value
|
|
* @param {any} index Current index
|
|
* @memberof InfiniteListItem
|
|
*/
|
|
constructor(list, value, index) {
|
|
this.value = value;
|
|
this.index = index;
|
|
this.next = z => (!z ? list.get(index + 1) : list.get(index + z));
|
|
this.previous = z => (!z ? list.get(index - 1) : list.get(index - z));
|
|
|
|
// Check if Symbol exists
|
|
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
|
|
/**
|
|
* ES6 Symbol.iterator
|
|
* @returns {Iterable.<*>}
|
|
*/
|
|
this[Symbol.iterator] = () => ({
|
|
next: () => ({
|
|
done: false,
|
|
value: list.get(index + 1)
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* toString method for pretty printing InfiniteListItem instance.
|
|
* @returns {String} Decycled and beautified string
|
|
*/
|
|
toString() {
|
|
return ('InfiniteListItem [ .. ' +
|
|
stringify(this.value) +
|
|
' .. ]')
|
|
};
|
|
}
|
|
|
|
class InfiniteList {
|
|
/**
|
|
* InfiniteList Constructor.
|
|
* Iterates infinitely until index value is found.
|
|
* Stores cache in closure so when the same index is requested again,
|
|
* it's returned immediately.
|
|
* @param {*} start Starting value
|
|
* @param {Function} next Function to find next item
|
|
* Accepts current value and optionally previous value
|
|
* @constructs InfiniteList
|
|
*/
|
|
constructor(start, next) {
|
|
|
|
// Closure magic!
|
|
let cache = [];
|
|
let j = 0;
|
|
|
|
/**
|
|
* Get InfiniteListItem at index.
|
|
* @param {Number} index A non-negative integer representing index
|
|
* @returns {InfiniteListItem}
|
|
*/
|
|
this.get = function (index) {
|
|
|
|
// Validation
|
|
if (
|
|
// i is a non-zero falsy value, or is negative
|
|
(isNonZeroFalsy(index) || index < 0)
|
|
|| !areNumbers(index)
|
|
) return;
|
|
|
|
//TODO: Cache limiting. (Removed for unexpected behaviour)
|
|
|
|
// Initializing first item if it doesn't exist
|
|
if (!cache[0]) cache[0] = start;
|
|
|
|
// If index were to be infinity, value and index are infinity
|
|
if (index === Infinity) return new InfiniteListItem(this, Infinity, Infinity)
|
|
|
|
// If index exists in cache, return the value
|
|
if (index in cache) return new InfiniteListItem(this, cache[index], index);
|
|
|
|
// If i doesn't exist in cache
|
|
if (!(index in cache)) {
|
|
if (cache.length <= index && (cache.length - 1) in cache)
|
|
while (cache.length <= index)
|
|
cache[cache.length] = next(cache[cache.length - 1], cache[cache.length - 2]);
|
|
}
|
|
return new InfiniteListItem(this, cache[index], index);
|
|
}
|
|
|
|
/**
|
|
* Clear cache manually.
|
|
* Forces destroy reference to cache, and creates a new cache.
|
|
* Old cache will be GC'd.
|
|
* @returns {undefined}
|
|
*/
|
|
this.clearCache = () => (cache = [], undefined);
|
|
|
|
// Check if Symbol exists
|
|
if (typeof Symbol !== 'undefined' && Symbol.iterator) {
|
|
/**
|
|
* ES6 Symbol.iterator
|
|
* @returns {Iterable.<*>}
|
|
*/
|
|
this[Symbol.iterator] = function () {
|
|
return {
|
|
next: () => ({
|
|
done: false,
|
|
value: this.get(j++)
|
|
})
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes a given number of elements from the InfiniteList.
|
|
* @param {Number} from Number of elements or starting index
|
|
* @param {Number} to Optional ending index
|
|
* @returns {Array<InfiniteListItem>} An array of InfiniteListItems
|
|
*/
|
|
InfiniteList.prototype.take = function (from, to) {
|
|
const arr = [];
|
|
|
|
if(
|
|
isNonZeroFalsy(from)
|
|
|| (from === 0 && isNonZeroFalsy(to)) // Take 0 elements?
|
|
|| (!areNumbers(from) && isNonZeroFalsy(to))
|
|
) return arr;
|
|
|
|
let source, target;
|
|
// "from" number of elements
|
|
if (isNonZeroFalsy(to)) {
|
|
source = 0;
|
|
target = from;
|
|
}
|
|
// "target" is the end index!
|
|
else {
|
|
source = from;
|
|
target = to + 1
|
|
};
|
|
|
|
for (let i = source; i < target; i++) {
|
|
arr.push(this.get(i));
|
|
};
|
|
return arr;
|
|
};
|
|
|
|
/**
|
|
* Returns first element of InfiniteList.
|
|
* @returns {InfiniteListItem} Instance of InfiniteListItem
|
|
*/
|
|
InfiniteList.prototype.top = function () {
|
|
return this.get(0)
|
|
};
|
|
|
|
/**
|
|
* Returns last element of InfiniteList (Infinity).
|
|
* @returns {InfiniteListItem} Instance of InfiniteListItem
|
|
*/
|
|
InfiniteList.prototype.end = function () {
|
|
return this.get(Infinity)
|
|
};
|
|
|
|
/**
|
|
* toString method for pretty printing InfiniteList instance.
|
|
* Snips at 2 elements for arrays and objects, or 5 elements otherwise.
|
|
* @returns {String} Pretty printed InfiniteList
|
|
*/
|
|
InfiniteList.prototype.toString = function () {
|
|
const length = typeof this.first() === 'object' ? 2 : 5;
|
|
return [
|
|
'InfiniteList [',
|
|
this
|
|
.take(length)
|
|
.map(x => (' ' + stringify(x.value))) +
|
|
',',
|
|
'... ]'
|
|
]
|
|
.join(' ');
|
|
}
|
|
|
|
/* Convenience methods */
|
|
InfiniteList.prototype.first = InfiniteList.prototype.top;
|
|
InfiniteList.prototype.last = InfiniteList.prototype.end;
|
|
|
|
// Exports
|
|
module.exports = InfiniteList;
|
|
module.exports.InfiniteListItem = InfiniteListItem;
|