diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b2a846..67d99ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to - Table of contents to documentation. - Get random element from array: `sample(array)`. - ECMAScript Modules support via importing '@metarhia/common/module'. +- `Iterator#keepLast()` to allow storing last returned iterator value + and nice interactions with `Iterator#takeWhile()`, + `Iterator#skipWhile()` etc. ### Changed diff --git a/README.md b/README.md index 8b1e18ff..a0c6a789 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ $ npm install @metarhia/common - [Iterator.prototype.forEach](#iteratorprototypeforeachfn-thisarg) - [Iterator.prototype.includes](#iteratorprototypeincludeselement) - [Iterator.prototype.join](#iteratorprototypejoinsep----prefix---suffix--) + - [Iterator.prototype.keepLast](#iteratorprototypekeeplast) - [Iterator.prototype.map](#iteratorprototypemapmapper-thisarg) - [Iterator.prototype.next](#iteratorprototypenext) - [Iterator.prototype.reduce](#iteratorprototypereducereducer-initialvalue) @@ -1130,6 +1131,23 @@ This iterator will call `mapper` on each element and if mapper returns NOT #### Iterator.prototype.join(sep = ', ', prefix = '', suffix = '') +#### Iterator.prototype.keepLast() + +_Returns:_ `` + +Creates an iterator that stores last returned value and allows reinserting + +it into the iterator. + +_Example:_ + +```js +const keeper = iter([1, 2, 3, 4]).keepLast(); +keeper.takeWhile(v => v < 3).toArray(); // [1, 2] +keeper.repeatLast(); +keeper.toArray(); // [3, 4]; +``` + #### Iterator.prototype.map(mapper, thisArg) #### Iterator.prototype.next() diff --git a/lib/iterator.js b/lib/iterator.js index 3d8e40aa..62d12f37 100644 --- a/lib/iterator.js +++ b/lib/iterator.js @@ -222,6 +222,18 @@ class Iterator { return iter(res && res[Symbol.iterator] ? res : [res]); } + // Creates an iterator that stores last returned value and allows reinserting + // it into the iterator. + // Returns: + // Example: + // const keeper = iter([1, 2, 3, 4]).keepLast(); + // keeper.takeWhile(v => v < 3).toArray(); // [1, 2] + // keeper.repeatLast(); + // keeper.toArray(); // [3, 4] + keepLast() { + return new KeepLastIterator(this); + } + // Create iterator iterating over the range // Signature: start, stop[, step] // start @@ -490,6 +502,39 @@ class SkipWhileIterator extends Iterator { } } +class KeepLastIterator extends Iterator { + constructor(base) { + super(base); + this.repeat = false; + this.lastElement = undefined; + } + + // Get last stored value + // Returns: last stored value or `undefined` + last() { + if (this.lastElement === undefined || this.lastElement.done) { + return undefined; + } + return this.lastElement.value; + } + + // Makes this iterator return last stored value upon next `next` call + // Returns: + repeatLast() { + this.repeat = true; + return this; + } + + next() { + if (this.repeat && this.lastElement !== undefined) { + this.repeat = false; + return this.lastElement; + } + this.lastElement = this.base.next(); + return this.lastElement; + } +} + const iter = base => new Iterator(base); module.exports = { diff --git a/test/iterator.js b/test/iterator.js index 390e8381..9e2f188d 100644 --- a/test/iterator.js +++ b/test/iterator.js @@ -643,6 +643,45 @@ metatests.testSync('Iterator.chainApply on string', test => { test.strictSame(actual, 'h e l l o'); }); +metatests.testSync('Iterator.keepLast empty', test => { + const actual = iter([]).keepLast(); + test.strictSame(actual.last(), undefined); + test.type(actual.repeatLast(), 'KeepLastIterator'); + test.strictSame(actual.next(), { done: true, value: undefined }); + test.strictSame(actual.next(), { done: true, value: undefined }); +}); + +metatests.testSync('Iterator.keepLast repeat', test => { + const actual = iter([1, 2, 3, 4]).keepLast(); + test.strictSame(actual.next(), { done: false, value: 1 }); + test.strictSame(actual.last(), 1); + + actual.repeatLast(); + test.strictSame(actual.last(), 1); + test.strictSame(actual.next(), { done: false, value: 1 }); + test.strictSame(actual.last(), 1); + + test.strictSame(actual.next(), { done: false, value: 2 }); + test.strictSame(actual.next(), { done: false, value: 3 }); + + actual.repeatLast(); + test.strictSame(actual.last(), 3); + test.strictSame(actual.next(), { done: false, value: 3 }); + test.strictSame(actual.last(), 3); + + test.strictSame(actual.next(), { done: false, value: 4 }); + test.strictSame(actual.next(), { done: true, value: undefined }); + test.strictSame(actual.last(), undefined); +}); + +metatests.testSync('Iterator.keepLast takeWhile', test => { + const keeper = iter([1, 2, 3, 4]).keepLast(); + test.strictSame(keeper.takeWhile(v => v < 3).toArray(), [1, 2]); + test.strictSame(keeper.last(), 3); + keeper.repeatLast(); + test.strictSame(keeper.toArray(), [3, 4]); +}); + metatests.testSync('iterEntries must iterate over object entries', test => { const source = { a: 13, b: 42, c: 'hello' }; test.strictSame(iterEntries(source).toArray(), Object.entries(source));