From a4b2bb98ca262b01de23bdc02efd5d5d5b75f070 Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Thu, 5 Oct 2017 11:46:04 +0200 Subject: [PATCH 1/9] POC for Sequence operations composition that enables separation of interface and implementation scope restricted to associateBy() operation --- src/Sequence.ts | 7 +++-- src/associateBy.ts | 62 ++++++++++++++++++++++++++++++---------- test/associateBy.test.ts | 31 ++++++++++++++++++++ 3 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index 7241963..a901645 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -33,7 +33,6 @@ import single from "./single"; import singleOrNull from "./singleOrNull"; import filterNot from "./filterNot"; import associate from "./associate"; -import associateBy from "./associateBy"; import groupBy from "./groupBy"; import reduce from "./reduce"; import reduceIndexed from "./reduceIndexed"; @@ -69,6 +68,10 @@ import dropWhile from "./dropWhile"; import takeWhile from "./takeWhile"; import asIterable from "./asIterable"; import merge from "./merge"; +import { AssociateBy, associateBy } from "./associateBy"; + +export default interface Sequence extends AssociateBy { +} /** * A Sequence accepts an iterator and provides a fluent functional API consisting @@ -131,7 +134,6 @@ export default class Sequence { single = single; singleOrNull = singleOrNull; associate = associate; - associateBy = associateBy; groupBy = groupBy; reduce = reduce; reduceIndexed = reduceIndexed; @@ -155,6 +157,7 @@ export default class Sequence { asIterable = asIterable; merge = merge; } +Sequence.prototype.associateBy = associateBy; export function sequenceOf(...args: Array): Sequence { return asSequence(args); diff --git a/src/associateBy.ts b/src/associateBy.ts index c56b9a8..44a8b9b 100644 --- a/src/associateBy.ts +++ b/src/associateBy.ts @@ -1,27 +1,59 @@ import Sequence from "./Sequence"; -/** - * Returns a map consisting of the elements mapped by the given `keySelector`. The value - * can optionally be transformed into another value by specifying a `valueTransformer`. - * - * @param {(value: T) => K} keySelector - * @param {(value: T) => V} valueTransform - * @returns {Map} - */ -function associateBy(this: Sequence, - keySelector: (value: T) => K, - valueTransform?: (value: T) => V): Map { +export interface AssociateBy { + + /** + * Returns a map consisting of the elements mapped by the given `keySelector`. + * + * @param {(value: T) => K} keySelector + * @returns {Map} + */ + associateBy(keySelector: (value: T) => K): Map; + + /** + * Returns a map consisting of the elements indexed by the given `key`. + * + * @param {K} key + * @returns {Map} + */ + associateBy(key: K): Map; + + /** + * Returns a map consisting of the elements mapped by the given `keySelector`. The value + * is transformed into another value by the `valueTransformer`. + * + * @param {(value: T) => K} keySelector + * @param {(value: T) => V} valueTransformer + * @returns {Map} + */ + associateBy(keySelector: (value: T) => K, valueTransformer: (value: T) => V): Map; + + /** + * Returns a map consisting of the elements indexed by the given `key`. The value + * is transformed into another value by the `valueTransformer`. + * + * @param {K} key + * @param {(value: T) => V} valueTransformer + * @returns {Map} + */ + associateBy(key: K, valueTransformer: (value: T) => V): Map; +} + +export function associateBy(this: Sequence, + keyOrSelector: any, + valueTransform?: (value: T) => V): Map { + const selector = typeof keyOrSelector === "function" + ? keyOrSelector + : (value: T) => value[keyOrSelector as keyof T]; const result = new Map(); const transform = valueTransform != null ? valueTransform : (value: T) => value; while (this.iterator.hasNext()) { const item = this.iterator.next(); - const key = keySelector(item); + const key = selector(item); const value = transform(item); result.set(key, value); } return result; -} - -export default associateBy; \ No newline at end of file +} \ No newline at end of file diff --git a/test/associateBy.test.ts b/test/associateBy.test.ts index 58cad05..ec2c6bf 100644 --- a/test/associateBy.test.ts +++ b/test/associateBy.test.ts @@ -15,6 +15,20 @@ describe("associateBy", () => { expect(map.get(3)).toBe(c); }); + it("should associate map by key", () => { + const a = {k: 1, v: 11}; + const b = {k: 2, v: 22}; + const c = {k: 3, v: 33}; + + const map = sequenceOf(a, b, c) + .associateBy("k"); + + expect(map.size).toBe(3); + expect(map.get(1)).toBe(a); + expect(map.get(2)).toBe(b); + expect(map.get(3)).toBe(c); + }); + it("should associate map by keySelector and valueTransformer", () => { const a = {k: 1, v: 11}; const b = {k: 2, v: 22}; @@ -32,6 +46,23 @@ describe("associateBy", () => { expect(map.get(3)).toBe(33); }); + it("should associate map by key and valueTransformer", () => { + const a = {k: 1, v: 11}; + const b = {k: 2, v: 22}; + const c = {k: 3, v: 33}; + + const map = sequenceOf(a, b, c) + .associateBy( + "k", + it => it.v + ); + + expect(map.size).toBe(3); + expect(map.get(1)).toBe(11); + expect(map.get(2)).toBe(22); + expect(map.get(3)).toBe(33); + }); + it("latest entries should win in case of duplicates", () => { const a = {k: 1, v: 11}; const b = {k: 2, v: 22}; From c8049cd191f212f3bf90e907217e8c19b07019fc Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Thu, 5 Oct 2017 12:27:20 +0200 Subject: [PATCH 2/9] Add warning and link in documentation to direct reader to actual public API --- src/associateBy.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/associateBy.ts b/src/associateBy.ts index 44a8b9b..436c787 100644 --- a/src/associateBy.ts +++ b/src/associateBy.ts @@ -39,6 +39,10 @@ export interface AssociateBy { associateBy(key: K, valueTransformer: (value: T) => V): Map; } +/** + * ## !!! IMPLEMENTATION DETAILS !!! + * ### The "real" documentation is here : [[Sequence.associateBy]] + */ export function associateBy(this: Sequence, keyOrSelector: any, valueTransform?: (value: T) => V): Map { From 4e1f9dd3c797acd589a16681c730c5e0ed70ec00 Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Mon, 9 Oct 2017 23:11:06 +0200 Subject: [PATCH 3/9] Try mixin approach --- src/Sequence.ts | 12 ++++++++++-- src/associateBy.ts | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index a901645..c6561a6 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -68,7 +68,7 @@ import dropWhile from "./dropWhile"; import takeWhile from "./takeWhile"; import asIterable from "./asIterable"; import merge from "./merge"; -import { AssociateBy, associateBy } from "./associateBy"; +import { AssociateBy } from "./associateBy"; export default interface Sequence extends AssociateBy { } @@ -157,7 +157,15 @@ export default class Sequence { asIterable = asIterable; merge = merge; } -Sequence.prototype.associateBy = associateBy; +applyMixins(Sequence, [AssociateBy]); + +function applyMixins(derivedCtor: any, baseCtors: any[]) { + baseCtors.forEach(baseCtor => { + Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { + derivedCtor.prototype[name] = baseCtor.prototype[name]; + }); + }); +} export function sequenceOf(...args: Array): Sequence { return asSequence(args); diff --git a/src/associateBy.ts b/src/associateBy.ts index 436c787..cc86dd2 100644 --- a/src/associateBy.ts +++ b/src/associateBy.ts @@ -39,25 +39,29 @@ export interface AssociateBy { associateBy(key: K, valueTransformer: (value: T) => V): Map; } -/** - * ## !!! IMPLEMENTATION DETAILS !!! - * ### The "real" documentation is here : [[Sequence.associateBy]] - */ -export function associateBy(this: Sequence, - keyOrSelector: any, - valueTransform?: (value: T) => V): Map { - const selector = typeof keyOrSelector === "function" - ? keyOrSelector - : (value: T) => value[keyOrSelector as keyof T]; - const result = new Map(); - const transform = valueTransform != null - ? valueTransform - : (value: T) => value; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const key = selector(item); - const value = transform(item); - result.set(key, value); +export class AssociateBy { + + /** + * ## !!! IMPLEMENTATION DETAILS !!! + * ### The "real" documentation is here : [[Sequence.associateBy]] + */ + associateBy(this: Sequence, + keyOrSelector: any, + valueTransform?: (value: T) => V): Map { + const selector = typeof keyOrSelector === "function" + ? keyOrSelector + : (value: T) => value[keyOrSelector as keyof T]; + const result = new Map(); + const transform = valueTransform != null + ? valueTransform + : (value: T) => value; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const key = selector(item); + const value = transform(item); + result.set(key, value); + } + return result; } - return result; + } \ No newline at end of file From fdfbea90c7703e073bfb85ad446d206c5e4ec30c Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Tue, 10 Oct 2017 11:46:25 +0200 Subject: [PATCH 4/9] Merge AssociateBy interface and class declarations --- src/associateBy.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/associateBy.ts b/src/associateBy.ts index cc86dd2..1272ffa 100644 --- a/src/associateBy.ts +++ b/src/associateBy.ts @@ -1,6 +1,6 @@ import Sequence from "./Sequence"; -export interface AssociateBy { +export class AssociateBy { /** * Returns a map consisting of the elements mapped by the given `keySelector`. @@ -37,14 +37,7 @@ export interface AssociateBy { * @returns {Map} */ associateBy(key: K, valueTransformer: (value: T) => V): Map; -} - -export class AssociateBy { - /** - * ## !!! IMPLEMENTATION DETAILS !!! - * ### The "real" documentation is here : [[Sequence.associateBy]] - */ associateBy(this: Sequence, keyOrSelector: any, valueTransform?: (value: T) => V): Map { From 7eb60c5c12c9841d34d319fac53104bb47078b5e Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Wed, 11 Oct 2017 18:04:17 +0200 Subject: [PATCH 5/9] Migrate average() to new mixin approach --- src/Sequence.ts | 15 +++++++-------- src/average.ts | 34 ++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index c6561a6..0878e9f 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -1,4 +1,4 @@ -import SequenceIterator, {GeneratorIterator, GeneratorSeedIterator, IterableIterator} from "./SequenceIterator"; +import SequenceIterator, { GeneratorIterator, GeneratorSeedIterator, IterableIterator } from "./SequenceIterator"; import map from "./map"; import filter from "./filter"; import flatMap from "./flatMap"; @@ -57,7 +57,6 @@ import sum from "./sum"; import sumBy from "./sumBy"; import chunk from "./chunk"; import reverse from "./reverse"; -import average from "./average"; import max from "./max"; import maxBy from "./maxBy"; import min from "./min"; @@ -69,8 +68,9 @@ import takeWhile from "./takeWhile"; import asIterable from "./asIterable"; import merge from "./merge"; import { AssociateBy } from "./associateBy"; +import { Average } from "./average"; -export default interface Sequence extends AssociateBy { +export default interface Sequence extends AssociateBy, Average { } /** @@ -147,7 +147,6 @@ export default class Sequence { unzip = unzip; sum = sum; sumBy = sumBy; - average = average; max = max; maxBy = maxBy; maxWith = maxWith; @@ -157,7 +156,7 @@ export default class Sequence { asIterable = asIterable; merge = merge; } -applyMixins(Sequence, [AssociateBy]); +applyMixins(Sequence, [AssociateBy, Average]); function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { @@ -189,8 +188,8 @@ export function asSequence(iterable: Iterable): Sequence { } export function generateSequence(nextFunction: () => T | null | undefined): Sequence; -export function generateSequence(seedFunction: () => T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; -export function generateSequence(seed: T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; +export function generateSequence(seedFunction: () => T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; +export function generateSequence(seed: T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; export function generateSequence(a: any, b?: any): Sequence { if (typeof a === "function" && b == null) { return new Sequence(new GeneratorIterator(a)); @@ -199,4 +198,4 @@ export function generateSequence(a: any, b?: any): Sequence { return seed != null ? new Sequence(new GeneratorSeedIterator(seed, b)) : emptySequence(); -} \ No newline at end of file +} diff --git a/src/average.ts b/src/average.ts index f1fcec2..0deab58 100644 --- a/src/average.ts +++ b/src/average.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Returns the average of all numbers of the sequence or `NaN` if the sequence is empty. - * - * @returns {number} - */ -function average(this: Sequence): number { - let sum = 0; - let count = 0; - while (this.iterator.hasNext()) { - sum += this.iterator.next(); - count++; +export class Average { + + /** + * Returns the average of all numbers of the sequence or `NaN` if the sequence is empty. + * + * @returns {number} + */ + average(this: Sequence): number { + let sum = 0; + let count = 0; + while (this.iterator.hasNext()) { + sum += this.iterator.next(); + count++; + } + return count === 0 + ? Number.NaN + : sum / count; } - return count === 0 - ? Number.NaN - : sum / count; -} -export default average; \ No newline at end of file +} \ No newline at end of file From 84e6fc5eb95fb738658eaf544f26f310d51a4c1d Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Thu, 12 Oct 2017 12:23:38 +0200 Subject: [PATCH 6/9] Migrate all operations to classes for new mixin approach --- src/Sequence.ts | 254 +++++++++++++++----------------------- src/all.ts | 30 ++--- src/any.ts | 36 +++--- src/asIterable.ts | 44 +++---- src/associate.ts | 34 ++--- src/average.ts | 2 +- src/chunk.ts | 50 ++++---- src/contains.ts | 30 ++--- src/count.ts | 44 +++---- src/distinct.ts | 22 ++-- src/distinctBy.ts | 26 ++-- src/drop.ts | 26 ++-- src/dropWhile.ts | 24 ++-- src/elementAt.ts | 36 +++--- src/elementAtOrElse.ts | 38 +++--- src/elementAtOrNull.ts | 36 +++--- src/filter.ts | 24 ++-- src/filterIndexed.ts | 26 ++-- src/filterNot.ts | 22 ++-- src/filterNotNull.ts | 20 +-- src/first.ts | 34 ++--- src/firstOrNull.ts | 40 +++--- src/flatMap.ts | 24 ++-- src/flatten.ts | 34 ++--- src/fold.ts | 36 +++--- src/foldIndexed.ts | 42 ++++--- src/forEach.ts | 24 ++-- src/forEachIndexed.ts | 24 ++-- src/groupBy.ts | 40 +++--- src/indexOf.ts | 34 ++--- src/indexOfFirst.ts | 36 +++--- src/indexOfLast.ts | 38 +++--- src/joinToString.ts | 84 +++++++------ src/last.ts | 42 ++++--- src/lastOrNull.ts | 42 ++++--- src/map.ts | 24 ++-- src/mapIndexed.ts | 24 ++-- src/mapNotNull.ts | 34 ++--- src/max.ts | 30 ++--- src/maxBy.ts | 40 +++--- src/maxWith.ts | 32 ++--- src/merge.ts | 66 +++++----- src/min.ts | 30 ++--- src/minBy.ts | 40 +++--- src/minWith.ts | 32 ++--- src/minus.ts | 38 +++--- src/none.ts | 38 +++--- src/onEach.ts | 28 +++-- src/partition.ts | 40 +++--- src/plus.ts | 60 +++++---- src/reduce.ts | 42 ++++--- src/reduceIndexed.ts | 46 +++---- src/reverse.ts | 24 ++-- src/single.ts | 44 +++---- src/singleOrNull.ts | 44 +++---- src/sorted.ts | 46 +++---- src/sortedBy.ts | 24 ++-- src/sortedByDescending.ts | 24 ++-- src/sortedDescending.ts | 20 +-- src/sortedWith.ts | 23 ++-- src/sum.ts | 26 ++-- src/sumBy.ts | 30 ++--- src/take.ts | 26 ++-- src/takeWhile.ts | 24 ++-- src/toArray.ts | 41 +++--- src/toMap.ts | 36 +++--- src/toSet.ts | 32 ++--- src/unzip.ts | 34 ++--- src/withIndex.ts | 22 ++-- src/zip.ts | 28 +++-- 70 files changed, 1366 insertions(+), 1224 deletions(-) diff --git a/src/Sequence.ts b/src/Sequence.ts index 0878e9f..00cca39 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -1,162 +1,102 @@ -import SequenceIterator, { GeneratorIterator, GeneratorSeedIterator, IterableIterator } from "./SequenceIterator"; -import map from "./map"; -import filter from "./filter"; -import flatMap from "./flatMap"; -import firstOrNull from "./firstOrNull"; -import first from "./first"; -import lastOrNull from "./lastOrNull"; -import onEach from "./onEach"; -import forEach from "./forEach"; -import toArray from "./toArray"; -import last from "./last"; -import all from "./all"; -import any from "./any"; -import none from "./none"; -import count from "./count"; -import distinct from "./distinct"; -import contains from "./contains"; -import indexOf from "./indexOf"; -import elementAt from "./elementAt"; -import elementAtOrNull from "./elementAtOrNull"; -import elementAtOrElse from "./elementAtOrElse"; -import indexOfFirst from "./indexOfFirst"; -import indexOfLast from "./indexOfLast"; -import joinToString from "./joinToString"; -import mapIndexed from "./mapIndexed"; -import withIndex from "./withIndex"; -import filterIndexed from "./filterIndexed"; -import forEachIndexed from "./forEachIndexed"; -import distinctBy from "./distinctBy"; -import drop from "./drop"; -import take from "./take"; -import single from "./single"; -import singleOrNull from "./singleOrNull"; -import filterNot from "./filterNot"; -import associate from "./associate"; -import groupBy from "./groupBy"; -import reduce from "./reduce"; -import reduceIndexed from "./reduceIndexed"; -import fold from "./fold"; -import foldIndexed from "./foldIndexed"; -import flatten from "./flatten"; -import sorted from "./sorted"; -import sortedBy from "./sortedBy"; -import sortedDescending from "./sortedDescending"; -import sortedByDescending from "./sortedByDescending"; -import sortedWith from "./sortedWith"; -import filterNotNull from "./filterNotNull"; -import mapNotNull from "./mapNotNull"; -import plus from "./plus"; -import minus from "./minus"; -import zip from "./zip"; -import unzip from "./unzip"; -import partition from "./partition"; -import toSet from "./toSet"; -import toMap from "./toMap"; -import sum from "./sum"; -import sumBy from "./sumBy"; -import chunk from "./chunk"; -import reverse from "./reverse"; -import max from "./max"; -import maxBy from "./maxBy"; -import min from "./min"; -import minBy from "./minBy"; -import maxWith from "./maxWith"; -import minWith from "./minWith"; -import dropWhile from "./dropWhile"; -import takeWhile from "./takeWhile"; -import asIterable from "./asIterable"; -import merge from "./merge"; -import { AssociateBy } from "./associateBy"; -import { Average } from "./average"; - -export default interface Sequence extends AssociateBy, Average { -} +import SequenceIterator, {GeneratorIterator, GeneratorSeedIterator, IterableIterator} from "./SequenceIterator"; +import {All} from "./all"; +import {Any} from "./any"; +import {AsIterable} from "./asIterable"; +import {Associate} from "./associate"; +import {AssociateBy} from "./associateBy"; +import {Average} from "./average"; +import {Chunk} from "./chunk"; +import {Contains} from "./contains"; +import {Count} from "./count"; +import {Distinct} from "./distinct"; +import {DistinctBy} from "./distinctBy"; +import {Drop} from "./drop"; +import {DropWhile} from "./dropWhile"; +import {ElementAt} from "./elementAt"; +import {ElementAtOrElse} from "./elementAtOrElse"; +import {ElementAtOrNull} from "./elementAtOrNull"; +import {Filter} from "./filter"; +import {FilterIndexed} from "./filterIndexed"; +import {FilterNot} from "./filterNot"; +import {FilterNotNull} from "./filterNotNull"; +import {First} from "./first"; +import {FirstOrNull} from "./firstOrNull"; +import {FlatMap} from "./flatMap"; +import {Flatten} from "./flatten"; +import {Fold} from "./fold"; +import {FoldIndexed} from "./foldIndexed"; +import {ForEach} from "./forEach"; +import {ForEachIndexed} from "./forEachIndexed"; +import {GroupBy} from "./groupBy"; +import {IndexOf} from "./indexOf"; +import {IndexOfFirst} from "./indexOfFirst"; +import {IndexOfLast} from "./indexOfLast"; +import {JoinToString} from "./joinToString"; +import {Last} from "./last"; +import {LastOrNull} from "./lastOrNull"; +import {Map} from "./map"; +import {MapIndexed} from "./mapIndexed"; +import {MapNotNull} from "./mapNotNull"; +import {Max} from "./max"; +import {MaxBy} from "./maxBy"; +import {MaxWith} from "./maxWith"; +import {Merge} from "./merge"; +import {Min} from "./min"; +import {MinBy} from "./minBy"; +import {Minus} from "./minus"; +import {MinWith} from "./minWith"; +import {None} from "./none"; +import {OnEach} from "./onEach"; +import {Partition} from "./partition"; +import {Plus} from "./plus"; +import {Reduce} from "./reduce"; +import {ReduceIndexed} from "./reduceIndexed"; +import {Reverse} from "./reverse"; +import {Single} from "./single"; +import {SingleOrNull} from "./singleOrNull"; +import {Sorted} from "./sorted"; +import {SortedBy} from "./sortedBy"; +import {SortedByDescending} from "./sortedByDescending"; +import {SortedDescending} from "./sortedDescending"; +import {SortedWith} from "./sortedWith"; +import {Sum} from "./sum"; +import {SumBy} from "./sumBy"; +import {Take} from "./take"; +import {TakeWhile} from "./takeWhile"; +import {ToArray} from "./toArray"; +import {ToMap} from "./toMap"; +import {ToSet} from "./toSet"; +import {Unzip} from "./unzip"; +import {WithIndex} from "./withIndex"; +import {Zip} from "./zip"; /** - * A Sequence accepts an iterator and provides a fluent functional API consisting + * A Sequence provides a fluent functional API consisting * of various intermediate and terminal operations for processing the iterated data. * The operations are evaluated lazily to avoid examining all of the input data * when it's not necessary. Sequences can be iterated only once. */ -export default class Sequence { +export default interface Sequence extends All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop, + DropWhile, ElementAt, ElementAtOrElse, ElementAtOrNull, Filter, FilterIndexed, FilterNot, FilterNotNull, First, FirstOrNull, FlatMap, Flatten, Fold, FoldIndexed, + ForEach, ForEachIndexed, GroupBy, IndexOf, IndexOfFirst, IndexOfLast, JoinToString, Last, LastOrNull, Map, MapIndexed, MapNotNull, Max, MaxBy, MaxWith, Merge, Min, MinBy, + Minus, MinWith, None, OnEach, Partition, Plus, Reduce, ReduceIndexed, Reverse, Single, SingleOrNull, Sorted, SortedBy, SortedByDescending, SortedDescending, SortedWith, + Sum, SumBy, Take, TakeWhile, ToArray, ToMap, ToSet, Unzip, WithIndex, Zip { + readonly iterator: SequenceIterator; +} + +/** + * @hidden + */ +class SequenceImpl { constructor(readonly iterator: SequenceIterator) { } - - map = map; - mapNotNull = mapNotNull; - mapIndexed = mapIndexed; - filter = filter; - filterNot = filterNot; - filterNotNull = filterNotNull; - filterIndexed = filterIndexed; - flatMap = flatMap; - distinct = distinct; - distinctBy = distinctBy; - withIndex = withIndex; - drop = drop; - dropWhile = dropWhile; - take = take; - takeWhile = takeWhile; - onEach = onEach; - flatten = flatten; - sorted = sorted; - sortedDescending = sortedDescending; - sortedBy = sortedBy; - sortedByDescending = sortedByDescending; - sortedWith = sortedWith; - reverse = reverse; - forEach = forEach; - forEachIndexed = forEachIndexed; - toArray = toArray; - toList = toArray; - toSet = toSet; - toMap = toMap; - first = first; - firstOrNull = firstOrNull; - last = last; - lastOrNull = lastOrNull; - find = firstOrNull; - findLast = lastOrNull; - all = all; - any = any; - none = none; - count = count; - contains = contains; - indexOf = indexOf; - indexOfFirst = indexOfFirst; - indexOfLast = indexOfLast; - elementAt = elementAt; - elementAtOrNull = elementAtOrNull; - elementAtOrElse = elementAtOrElse; - joinTo = joinToString; - joinToString = joinToString; - single = single; - singleOrNull = singleOrNull; - associate = associate; - groupBy = groupBy; - reduce = reduce; - reduceIndexed = reduceIndexed; - fold = fold; - foldIndexed = foldIndexed; - partition = partition; - chunk = chunk; - plus = plus; - minus = minus; - zip = zip; - unzip = unzip; - sum = sum; - sumBy = sumBy; - max = max; - maxBy = maxBy; - maxWith = maxWith; - min = min; - minBy = minBy; - minWith = minWith; - asIterable = asIterable; - merge = merge; } -applyMixins(Sequence, [AssociateBy, Average]); + +applyMixins(SequenceImpl, [All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop, + DropWhile, ElementAt, ElementAtOrElse, ElementAtOrNull, Filter, FilterIndexed, FilterNot, FilterNotNull, First, FirstOrNull, FlatMap, Flatten, Fold, FoldIndexed, + ForEach, ForEachIndexed, GroupBy, IndexOf, IndexOfFirst, IndexOfLast, JoinToString, Last, LastOrNull, Map, MapIndexed, MapNotNull, Max, MaxBy, MaxWith, Merge, Min, MinBy, + Minus, MinWith, None, OnEach, Partition, Plus, Reduce, ReduceIndexed, Reverse, Single, SingleOrNull, Sorted, SortedBy, SortedByDescending, SortedDescending, SortedWith, + Sum, SumBy, Take, TakeWhile, ToArray, ToMap, ToSet, Unzip, WithIndex, Zip]); function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { @@ -184,7 +124,15 @@ export function asSequence(iterable: Iterable): Sequence { if (iterable[Symbol.iterator] == null) { throw new Error("Cannot create sequence for non-iterable input: " + iterable); } - return new Sequence(new IterableIterator(iterable)); + return createSequence(new IterableIterator(iterable)); +} + +export function createSequence(iterator: SequenceIterator): Sequence { + return new SequenceImpl(iterator) as any; +} + +export function isSequence(object: any): object is Sequence { + return object instanceof SequenceImpl; } export function generateSequence(nextFunction: () => T | null | undefined): Sequence; @@ -192,10 +140,10 @@ export function generateSequence(seedFunction: () => T | null | undefined, ne export function generateSequence(seed: T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; export function generateSequence(a: any, b?: any): Sequence { if (typeof a === "function" && b == null) { - return new Sequence(new GeneratorIterator(a)); + return createSequence(new GeneratorIterator(a)); } const seed = typeof a === "function" ? a() : a; return seed != null - ? new Sequence(new GeneratorSeedIterator(seed, b)) + ? createSequence(new GeneratorSeedIterator(seed, b)) : emptySequence(); } diff --git a/src/all.ts b/src/all.ts index f96a7b5..76af63c 100644 --- a/src/all.ts +++ b/src/all.ts @@ -1,19 +1,21 @@ import Sequence from "./Sequence"; -/** - * Returns `true` if all elements match the given `predicate`. - * - * @param {(T) => boolean} predicate - * @returns {boolean} - */ -function all(this: Sequence, predicate: (T) => boolean): boolean { - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (!predicate(item)) { - return false; +export class All { + + /** + * Returns `true` if all elements match the given `predicate`. + * + * @param {(T) => boolean} predicate + * @returns {boolean} + */ + all(this: Sequence, predicate: (T) => boolean): boolean { + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (!predicate(item)) { + return false; + } } + return true; } - return true; -} -export default all; \ No newline at end of file +} \ No newline at end of file diff --git a/src/any.ts b/src/any.ts index 1ec42b0..8b0f119 100644 --- a/src/any.ts +++ b/src/any.ts @@ -1,22 +1,24 @@ import Sequence from "./Sequence"; -/** - * Returns `true` if at least one element match the given `predicate`. - * - * @param {(T) => boolean} predicate - * @returns {boolean} - */ -function any(this: Sequence, predicate?: (T) => boolean): boolean { - if (predicate == null) { - return this.iterator.hasNext(); - } - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { - return true; +export class Any { + + /** + * Returns `true` if at least one element match the given `predicate`. + * + * @param {(T) => boolean} predicate + * @returns {boolean} + */ + any(this: Sequence, predicate?: (T) => boolean): boolean { + if (predicate == null) { + return this.iterator.hasNext(); + } + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + return true; + } } + return false; } - return false; -} -export default any; \ No newline at end of file +} \ No newline at end of file diff --git a/src/asIterable.ts b/src/asIterable.ts index 178630d..b325b7f 100644 --- a/src/asIterable.ts +++ b/src/asIterable.ts @@ -1,25 +1,27 @@ import Sequence from "./Sequence"; -/** - * Returns an iterable representation of the sequence. - * - * @returns {Iterable} - */ -function asIterable(this: Sequence): Iterable { - const iterator = this.iterator; - return { - [Symbol.iterator](): Iterator { - return { - next(): IteratorResult { - if (!iterator.hasNext()) { - return {done: true, value: undefined} as any as IteratorResult; +export class AsIterable { + + /** + * Returns an iterable representation of the sequence. + * + * @returns {Iterable} + */ + asIterable(this: Sequence): Iterable { + const iterator = this.iterator; + return { + [Symbol.iterator](): Iterator { + return { + next(): IteratorResult { + if (!iterator.hasNext()) { + return { done: true, value: undefined } as any as IteratorResult; + } + const item = iterator.next(); + return { done: false, value: item } as any as IteratorResult; } - const item = iterator.next(); - return {done: false, value: item} as any as IteratorResult; - } - }; - } - }; -} + }; + } + }; + } -export default asIterable; \ No newline at end of file +} \ No newline at end of file diff --git a/src/associate.ts b/src/associate.ts index 8ecb8be..3ab8178 100644 --- a/src/associate.ts +++ b/src/associate.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Transforms each element into a key-value pair and returns the results as map. In case of - * duplicate keys the last key-value pair overrides the other. - * - * @param {(value: T) => [K , V]} transform - * @returns {Map} - */ -function associate(this: Sequence, transform: (value: T) => [K, V]): Map { - const result = new Map(); - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const pair = transform(item); - result.set(pair[0], pair[1]); +export class Associate { + + /** + * Transforms each element into a key-value pair and returns the results as map. In case of + * duplicate keys the last key-value pair overrides the other. + * + * @param {(value: T) => [K , V]} transform + * @returns {Map} + */ + associate(this: Sequence, transform: (value: T) => [K, V]): Map { + const result = new Map(); + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const pair = transform(item); + result.set(pair[0], pair[1]); + } + return result; } - return result; -} -export default associate; \ No newline at end of file +} \ No newline at end of file diff --git a/src/average.ts b/src/average.ts index 0deab58..40a3ba1 100644 --- a/src/average.ts +++ b/src/average.ts @@ -1,6 +1,6 @@ import Sequence from "./Sequence"; -export class Average { +export class Average { /** * Returns the average of all numbers of the sequence or `NaN` if the sequence is empty. diff --git a/src/chunk.ts b/src/chunk.ts index 5a06bf5..bcd0a59 100644 --- a/src/chunk.ts +++ b/src/chunk.ts @@ -1,29 +1,31 @@ import Sequence from "./Sequence"; -/** - * Splits the elements of the sequence into arrays which length is determined by - * the given `chunkSize` and returns all chunks as array. - * - * @param {number} chunkSize - * @returns {Array>} - */ -function chunk(this: Sequence, chunkSize: number): Array> { - if (chunkSize < 1) { - throw new Error("chunkSize must be > 0 but is " + chunkSize); - } - const result: Array> = []; - let index = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const chunkIndex = Math.floor(index / chunkSize); - if (result[chunkIndex] == null) { - result[chunkIndex] = [item]; - } else { - result[chunkIndex].push(item); +export class Chunk { + + /** + * Splits the elements of the sequence into arrays which length is determined by + * the given `chunkSize` and returns all chunks as array. + * + * @param {number} chunkSize + * @returns {Array>} + */ + chunk(this: Sequence, chunkSize: number): Array> { + if (chunkSize < 1) { + throw new Error("chunkSize must be > 0 but is " + chunkSize); + } + const result: Array> = []; + let index = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const chunkIndex = Math.floor(index / chunkSize); + if (result[chunkIndex] == null) { + result[chunkIndex] = [item]; + } else { + result[chunkIndex].push(item); + } + index++; } - index++; + return result; } - return result; -} -export default chunk; \ No newline at end of file +} \ No newline at end of file diff --git a/src/contains.ts b/src/contains.ts index ace4b44..f29db19 100644 --- a/src/contains.ts +++ b/src/contains.ts @@ -1,19 +1,21 @@ import Sequence from "./Sequence"; -/** - * Returns `true` if the sequence contains the given `element`. - * - * @param {T} element - * @returns {boolean} - */ -function contains(this: Sequence, element: T): boolean { - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (element === item) { - return true; +export class Contains { + + /** + * Returns `true` if the sequence contains the given `element`. + * + * @param {T} element + * @returns {boolean} + */ + contains(this: Sequence, element: T): boolean { + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (element === item) { + return true; + } } + return false; } - return false; -} -export default contains; \ No newline at end of file +} \ No newline at end of file diff --git a/src/count.ts b/src/count.ts index dc1624e..784df78 100644 --- a/src/count.ts +++ b/src/count.ts @@ -1,28 +1,30 @@ import Sequence from "./Sequence"; -/** - * Returns the number of elements of this sequence. If `predicate` is present, returns - * the number of elements matching the given `predicate`. - * - * @param {(T) => boolean} predicate - * @returns {number} - */ -function count(this: Sequence, predicate?: (T) => boolean): number { - let num = 0; - if (predicate == null) { - while (this.iterator.hasNext()) { - this.iterator.next(); - num++; - } - } else { - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { +export class Count { + + /** + * Returns the number of elements of this sequence. If `predicate` is present, returns + * the number of elements matching the given `predicate`. + * + * @param {(T) => boolean} predicate + * @returns {number} + */ + count(this: Sequence, predicate?: (T) => boolean): number { + let num = 0; + if (predicate == null) { + while (this.iterator.hasNext()) { + this.iterator.next(); num++; } + } else { + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + num++; + } + } } + return num; } - return num; -} -export default count; \ No newline at end of file +} \ No newline at end of file diff --git a/src/distinct.ts b/src/distinct.ts index e54c229..3e1b177 100644 --- a/src/distinct.ts +++ b/src/distinct.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import SequenceIterator from "./SequenceIterator"; class DistinctIterator implements SequenceIterator { @@ -36,13 +36,15 @@ class DistinctIterator implements SequenceIterator { } } -/** - * Returns a new sequence which discards all duplicate elements. - * - * @returns {Sequence} - */ -function distinct(this: Sequence): Sequence { - return new Sequence(new DistinctIterator(this.iterator)); -} +export class Distinct { + + /** + * Returns a new sequence which discards all duplicate elements. + * + * @returns {Sequence} + */ + distinct(this: Sequence): Sequence { + return createSequence(new DistinctIterator(this.iterator)); + } -export default distinct; \ No newline at end of file +} \ No newline at end of file diff --git a/src/distinctBy.ts b/src/distinctBy.ts index 2a7690d..2538660 100644 --- a/src/distinctBy.ts +++ b/src/distinctBy.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import SequenceIterator from "./SequenceIterator"; class DistinctByIterator implements SequenceIterator { @@ -40,15 +40,17 @@ class DistinctByIterator implements SequenceIterator { } } -/** - * Returns a new sequence which discards all elements with duplicate items determined - * by the given `selector`. - * - * @param {(item: T) => K} selector - * @returns {Sequence} - */ -function distinctBy(this: Sequence, selector: (item: T) => K): Sequence { - return new Sequence(new DistinctByIterator(this.iterator, selector)); -} +export class DistinctBy { + + /** + * Returns a new sequence which discards all elements with duplicate items determined + * by the given `selector`. + * + * @param {(item: T) => K} selector + * @returns {Sequence} + */ + distinctBy(this: Sequence, selector: (item: T) => K): Sequence { + return createSequence(new DistinctByIterator(this.iterator, selector)); + } -export default distinctBy; +} \ No newline at end of file diff --git a/src/drop.ts b/src/drop.ts index d1da009..b2bc82b 100644 --- a/src/drop.ts +++ b/src/drop.ts @@ -1,15 +1,17 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence which discards the first `n` elements; - * - * @param {number} n - * @returns {Sequence} - */ -function drop(this: Sequence, n: number): Sequence { - return this.withIndex() - .dropWhile(it => it.index < n) - .map(it => it.value); -} +export class Drop { -export default drop; \ No newline at end of file + /** + * Returns a new sequence which discards the first `n` elements; + * + * @param {number} n + * @returns {Sequence} + */ + drop(this: Sequence, n: number): Sequence { + return this.withIndex() + .dropWhile(it => it.index < n) + .map(it => it.value); + } + +} \ No newline at end of file diff --git a/src/dropWhile.ts b/src/dropWhile.ts index 126e6f6..9502b9a 100644 --- a/src/dropWhile.ts +++ b/src/dropWhile.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import SequenceIterator from "./SequenceIterator"; class DropWhileIterator implements SequenceIterator { @@ -44,14 +44,16 @@ class DropWhileIterator implements SequenceIterator { } } -/** - * Drops all elements of the sequence as long as the given `predicate` evaluates to true. - * - * @param {(item: T) => boolean} predicate - * @returns {Sequence} - */ -function dropWhile(this: Sequence, predicate: (item: T) => boolean): Sequence { - return new Sequence(new DropWhileIterator(this.iterator, predicate)); -} +export class DropWhile { + + /** + * Drops all elements of the sequence as long as the given `predicate` evaluates to true. + * + * @param {(item: T) => boolean} predicate + * @returns {Sequence} + */ + dropWhile(this: Sequence, predicate: (item: T) => boolean): Sequence { + return createSequence(new DropWhileIterator(this.iterator, predicate)); + } -export default dropWhile; \ No newline at end of file +} \ No newline at end of file diff --git a/src/elementAt.ts b/src/elementAt.ts index 05b703e..9515267 100644 --- a/src/elementAt.ts +++ b/src/elementAt.ts @@ -1,22 +1,24 @@ import Sequence from "./Sequence"; -/** - * Returns the element at position `index` (zero-based) or throws an error if `index` - * is out of bounds. - * - * @param {number} index - * @returns {T} - */ -function elementAt(this: Sequence, index: number): T { - let i = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (i === index) { - return item; +export class ElementAt { + + /** + * Returns the element at position `index` (zero-based) or throws an error if `index` + * is out of bounds. + * + * @param {number} index + * @returns {T} + */ + elementAt(this: Sequence, index: number): T { + let i = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (i === index) { + return item; + } + i++; } - i++; + throw new Error("Index out of bounds: " + index); } - throw new Error("Index out of bounds: " + index); -} -export default elementAt; \ No newline at end of file +} \ No newline at end of file diff --git a/src/elementAtOrElse.ts b/src/elementAtOrElse.ts index 392d320..1878f33 100644 --- a/src/elementAtOrElse.ts +++ b/src/elementAtOrElse.ts @@ -1,23 +1,25 @@ import Sequence from "./Sequence"; -/** - * Returns the element at position `index` (zero-based). If `index` is out of bounds returns - * the result of the given `defaultValue` function. - * - * @param {number} index - * @param defaultValue - * @returns {T} - */ -function elementAtOrElse(this: Sequence, index: number, defaultValue: (index: number) => T): T { - let i = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (i === index) { - return item; +export class ElementAtOrElse { + + /** + * Returns the element at position `index` (zero-based). If `index` is out of bounds returns + * the result of the given `defaultValue` function. + * + * @param {number} index + * @param defaultValue + * @returns {T} + */ + elementAtOrElse(this: Sequence, index: number, defaultValue: (index: number) => T): T { + let i = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (i === index) { + return item; + } + i++; } - i++; + return defaultValue(index); } - return defaultValue(index); -} -export default elementAtOrElse; \ No newline at end of file +} \ No newline at end of file diff --git a/src/elementAtOrNull.ts b/src/elementAtOrNull.ts index c4bcda1..58b3fd6 100644 --- a/src/elementAtOrNull.ts +++ b/src/elementAtOrNull.ts @@ -1,22 +1,24 @@ import Sequence from "./Sequence"; -/** - * Returns the element at position `index` (zero-based) or `null` if `index` - * is out of bounds. - * - * @param {number} index - * @returns {T} - */ -function elementAtOrNull(this: Sequence, index: number): T | null { - let i = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (i === index) { - return item; +export class ElementAtOrNull { + + /** + * Returns the element at position `index` (zero-based) or `null` if `index` + * is out of bounds. + * + * @param {number} index + * @returns {T} + */ + elementAtOrNull(this: Sequence, index: number): T | null { + let i = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (i === index) { + return item; + } + i++; } - i++; + return null; } - return null; -} -export default elementAtOrNull; \ No newline at end of file +} \ No newline at end of file diff --git a/src/filter.ts b/src/filter.ts index 24762fe..3377e72 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -1,5 +1,5 @@ import SequenceIterator from "./SequenceIterator"; -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; class FilterIterator implements SequenceIterator { private nextItem: T | undefined; @@ -37,14 +37,16 @@ class FilterIterator implements SequenceIterator { } } -/** - * Returns a new sequence consisting of all elements that match the given `predicate`. - * - * @param {(T) => boolean} predicate - * @returns {Sequence} - */ -function filter(this: Sequence, predicate: (T) => boolean): Sequence { - return new Sequence(new FilterIterator(predicate, this.iterator)); -} +export class Filter { + + /** + * Returns a new sequence consisting of all elements that match the given `predicate`. + * + * @param {(T) => boolean} predicate + * @returns {Sequence} + */ + filter(this: Sequence, predicate: (T) => boolean): Sequence { + return createSequence(new FilterIterator(predicate, this.iterator)); + } -export default filter; \ No newline at end of file +} \ No newline at end of file diff --git a/src/filterIndexed.ts b/src/filterIndexed.ts index 546b891..30b97a3 100644 --- a/src/filterIndexed.ts +++ b/src/filterIndexed.ts @@ -1,15 +1,17 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence consisting of all elements that match the given `predicate`. - * - * @param {(index: number, value: T) => boolean} predicate - * @returns {Sequence} - */ -function filterIndexed(this: Sequence, predicate: (index: number, value: T) => boolean): Sequence { - return this.withIndex() - .filter(it => predicate(it.index, it.value)) - .map(it => it.value); -} +export class FilterIndexed { -export default filterIndexed; \ No newline at end of file + /** + * Returns a new sequence consisting of all elements that match the given `predicate`. + * + * @param {(index: number, value: T) => boolean} predicate + * @returns {Sequence} + */ + filterIndexed(this: Sequence, predicate: (index: number, value: T) => boolean): Sequence { + return this.withIndex() + .filter(it => predicate(it.index, it.value)) + .map(it => it.value); + } + +} \ No newline at end of file diff --git a/src/filterNot.ts b/src/filterNot.ts index dad2b9a..87bf57d 100644 --- a/src/filterNot.ts +++ b/src/filterNot.ts @@ -1,13 +1,15 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence consisting of all elements that don't match the given `predicate`. - * - * @param {(value: T) => boolean} predicate - * @returns {Sequence} - */ -function filterNot(this: Sequence, predicate: (value: T) => boolean): Sequence { - return this.filter((value: T) => !predicate(value)); -} +export class FilterNot { -export default filterNot; \ No newline at end of file + /** + * Returns a new sequence consisting of all elements that don't match the given `predicate`. + * + * @param {(value: T) => boolean} predicate + * @returns {Sequence} + */ + filterNot(this: Sequence, predicate: (value: T) => boolean): Sequence { + return this.filter((value: T) => !predicate(value)); + } + +} \ No newline at end of file diff --git a/src/filterNotNull.ts b/src/filterNotNull.ts index dc595c2..a33a0f5 100644 --- a/src/filterNotNull.ts +++ b/src/filterNotNull.ts @@ -1,12 +1,14 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence consisting of all non-null elements. - * - * @returns {Sequence} - */ -function filterNotNull(this: Sequence): Sequence { - return this.filter(it => it !== null); -} +export class FilterNotNull { -export default filterNotNull; \ No newline at end of file + /** + * Returns a new sequence consisting of all non-null elements. + * + * @returns {Sequence} + */ + filterNotNull(this: Sequence): Sequence { + return this.filter(it => it !== null); + } + +} \ No newline at end of file diff --git a/src/first.ts b/src/first.ts index 0e443f0..c04ed43 100644 --- a/src/first.ts +++ b/src/first.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise throws - * an error. - * - * @param {(T) => boolean} predicate - * @returns {T} - */ -function first(this: Sequence, predicate?: (T) => boolean): T { - if (predicate != null) { - return this.filter(predicate).first(); - } - if (!this.iterator.hasNext()) { - throw new Error("No such element"); +export class First { + + /** + * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise throws + * an error. + * + * @param {(T) => boolean} predicate + * @returns {T} + */ + first(this: Sequence, predicate?: (T) => boolean): T { + if (predicate != null) { + return this.filter(predicate).first(); + } + if (!this.iterator.hasNext()) { + throw new Error("No such element"); + } + return this.iterator.next(); } - return this.iterator.next(); -} -export default first; \ No newline at end of file +} \ No newline at end of file diff --git a/src/firstOrNull.ts b/src/firstOrNull.ts index 887d284..ae5399e 100644 --- a/src/firstOrNull.ts +++ b/src/firstOrNull.ts @@ -1,18 +1,30 @@ import Sequence from "./Sequence"; -/** - * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise returns `null`. - * - * @param {(T) => boolean} predicate - * @returns {T} - */ -function firstOrNull(this: Sequence, predicate?: (T) => boolean): T | null { - if (predicate != null) { - return this.filter(predicate).firstOrNull(); +export class FirstOrNull { + + /** + * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise returns `null`. + * + * @param {(T) => boolean} predicate + * @returns {T} + */ + firstOrNull(this: Sequence, predicate?: (T) => boolean): T | null { + if (predicate != null) { + return this.filter(predicate).firstOrNull(); + } + return this.iterator.hasNext() + ? this.iterator.next() + : null; + } + + /** + * Returns the first element of the sequence or the first element matching `predicate` if present, otherwise returns `null`. + * + * @param {(T) => boolean} predicate + * @returns {T} + */ + find(this: Sequence, predicate?: (T) => boolean): T | null { + return this.firstOrNull(predicate); } - return this.iterator.hasNext() - ? this.iterator.next() - : null; -} -export default firstOrNull; \ No newline at end of file +} \ No newline at end of file diff --git a/src/flatMap.ts b/src/flatMap.ts index cbd503d..ebbc62a 100644 --- a/src/flatMap.ts +++ b/src/flatMap.ts @@ -1,5 +1,5 @@ import SequenceIterator from "./SequenceIterator"; -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; class FlatMapIterator implements SequenceIterator { private current: SequenceIterator | undefined; @@ -37,14 +37,16 @@ class FlatMapIterator implements SequenceIterator { } } -/** - * Transforms each element into a sequence of items and returns a flat single sequence of all those items. - * - * @param {(value: S) => Sequence} transform - * @returns {Sequence} - */ -function flatMap(this: Sequence, transform: (value: S) => Sequence): Sequence { - return new Sequence(new FlatMapIterator(transform, this.iterator)); -} +export class FlatMap { + + /** + * Transforms each element into a sequence of items and returns a flat single sequence of all those items. + * + * @param {(value: S) => Sequence} transform + * @returns {Sequence} + */ + flatMap(this: Sequence, transform: (value: S) => Sequence): Sequence { + return createSequence(new FlatMapIterator(transform, this.iterator)); + } -export default flatMap; \ No newline at end of file +} \ No newline at end of file diff --git a/src/flatten.ts b/src/flatten.ts index ee92fb5..9754b2f 100644 --- a/src/flatten.ts +++ b/src/flatten.ts @@ -1,18 +1,20 @@ -import Sequence, {asSequence} from "./Sequence"; +import Sequence, {asSequence, isSequence} from "./Sequence"; -/** - * Returns a single flat sequence of all the items from all sequences or iterables. - * - * @returns {Sequence} - */ -function flatten(this: Sequence | Iterable>): Sequence { - return this.flatMap(it => { - if (it instanceof Sequence) { - return it; - } else { - return asSequence(it); - } - }); -} +export class Flatten { -export default flatten; \ No newline at end of file + /** + * Returns a single flat sequence of all the items from all sequences or iterables. + * + * @returns {Sequence} + */ + flatten(this: Sequence | Iterable>): Sequence { + return this.flatMap(it => { + if (isSequence(it)) { + return it; + } else { + return asSequence(it); + } + }); + } + +} \ No newline at end of file diff --git a/src/fold.ts b/src/fold.ts index a59523e..6867b31 100644 --- a/src/fold.ts +++ b/src/fold.ts @@ -1,21 +1,23 @@ import Sequence from "./Sequence"; -/** - * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with - * the `initial` value. The result of the last operation will be passed as accumulated value to the next invocation - * of the operation until all elements of the sequence are processed. - * - * @param {R} initial - * @param {(acc: R, element: T) => R} operation - * @returns {R} - */ -function fold(this: Sequence, initial: R, operation: (acc: R, element: T) => R): R { - let result = initial; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result = operation(result, item); +export class Fold { + + /** + * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with + * the `initial` value. The result of the last operation will be passed as accumulated value to the next invocation + * of the operation until all elements of the sequence are processed. + * + * @param {R} initial + * @param {(acc: R, element: T) => R} operation + * @returns {R} + */ + fold(this: Sequence, initial: R, operation: (acc: R, element: T) => R): R { + let result = initial; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result = operation(result, item); + } + return result; } - return result; -} -export default fold; \ No newline at end of file +} \ No newline at end of file diff --git a/src/foldIndexed.ts b/src/foldIndexed.ts index 0c2404d..f50dde6 100644 --- a/src/foldIndexed.ts +++ b/src/foldIndexed.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with - * the `initial` value. The result of the last operation will be passed as accumulated value to the next invocation - * of the operation as well as the `index` of the current element (zero-based) until all elements of the sequence - * are processed. - * - * @param {R} initial - * @param {(index: number, acc: R, element: T) => R} operation - * @returns {R} - */ -function foldIndexed(this: Sequence, initial: R, operation: (index: number, acc: R, element: T) => R): R { - let result = initial; - let index = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result = operation(index, result, item); - index++; +export class FoldIndexed { + + /** + * Accumulates all elements of the sequence into a single result by applying the given `operation` starting with + * the `initial` value. The result of the last operation will be passed as accumulated value to the next invocation + * of the operation as well as the `index` of the current element (zero-based) until all elements of the sequence + * are processed. + * + * @param {R} initial + * @param {(index: number, acc: R, element: T) => R} operation + * @returns {R} + */ + foldIndexed(this: Sequence, initial: R, operation: (index: number, acc: R, element: T) => R): R { + let result = initial; + let index = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result = operation(index, result, item); + index++; + } + return result; } - return result; -} -export default foldIndexed; \ No newline at end of file +} \ No newline at end of file diff --git a/src/forEach.ts b/src/forEach.ts index 5ae1b21..89fef9e 100644 --- a/src/forEach.ts +++ b/src/forEach.ts @@ -1,15 +1,17 @@ import Sequence from "./Sequence"; -/** - * Performs the given `action` (side-effect) for each element of the sequence. - * - * @param {(T) => void} action - */ -function forEach(this: Sequence, action: (T) => void) { - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - action(item); +export class ForEach { + + /** + * Performs the given `action` (side-effect) for each element of the sequence. + * + * @param {(T) => void} action + */ + forEach(this: Sequence, action: (T) => void) { + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + action(item); + } } -} -export default forEach; \ No newline at end of file +} \ No newline at end of file diff --git a/src/forEachIndexed.ts b/src/forEachIndexed.ts index 5c57ad4..920746a 100644 --- a/src/forEachIndexed.ts +++ b/src/forEachIndexed.ts @@ -1,14 +1,16 @@ import Sequence from "./Sequence"; -/** - * Performs the given `action` (side-effect) for each element of the sequence and passes the `index` of the current - * element (zero-based). - * - * @param {(index: number, value: T) => void} action - */ -function forEachIndexed(this: Sequence, action: (index: number, value: T) => void) { - this.withIndex() - .forEach(it => action(it.index, it.value)); -} +export class ForEachIndexed { -export default forEachIndexed; \ No newline at end of file + /** + * Performs the given `action` (side-effect) for each element of the sequence and passes the `index` of the current + * element (zero-based). + * + * @param {(index: number, value: T) => void} action + */ + forEachIndexed(this: Sequence, action: (index: number, value: T) => void) { + this.withIndex() + .forEach(it => action(it.index, it.value)); + } + +} \ No newline at end of file diff --git a/src/groupBy.ts b/src/groupBy.ts index 15f667f..74cc10a 100644 --- a/src/groupBy.ts +++ b/src/groupBy.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Groups all elements of the sequence into a map. Keys are determined by the given `keySelector` function. - * - * @param {(value: T) => K} keySelector - * @returns {Map>} - */ -function groupBy(this: Sequence, keySelector: (value: T) => K): Map> { - const result = new Map>(); - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const key = keySelector(item); - const array = result.get(key); - if (array == null) { - result.set(key, [item]); - } else { - array.push(item); +export class GroupBy { + + /** + * Groups all elements of the sequence into a map. Keys are determined by the given `keySelector` function. + * + * @param {(value: T) => K} keySelector + * @returns {Map>} + */ + groupBy(this: Sequence, keySelector: (value: T) => K): Map> { + const result = new Map>(); + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const key = keySelector(item); + const array = result.get(key); + if (array == null) { + result.set(key, [item]); + } else { + array.push(item); + } } + return result; } - return result; -} -export default groupBy; \ No newline at end of file +} \ No newline at end of file diff --git a/src/indexOf.ts b/src/indexOf.ts index 022971d..9a0064b 100644 --- a/src/indexOf.ts +++ b/src/indexOf.ts @@ -1,21 +1,23 @@ import Sequence from "./Sequence"; -/** - * Returns the zero-based index of the given `element` or -1 if the sequence does not contain the element. - * - * @param {T} element - * @returns {number} - */ -function indexOf(this: Sequence, element: T): number { - let index = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (item === element) { - return index; +export class IndexOf { + + /** + * Returns the zero-based index of the given `element` or -1 if the sequence does not contain the element. + * + * @param {T} element + * @returns {number} + */ + indexOf(this: Sequence, element: T): number { + let index = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (item === element) { + return index; + } + index++; } - index++; + return -1; } - return -1; -} -export default indexOf; \ No newline at end of file +} \ No newline at end of file diff --git a/src/indexOfFirst.ts b/src/indexOfFirst.ts index a3d9b51..b20be76 100644 --- a/src/indexOfFirst.ts +++ b/src/indexOfFirst.ts @@ -1,22 +1,24 @@ import Sequence from "./Sequence"; -/** - * Returns the zero-based index of the first element matching the given `predicate` or -1 if no element matches - * the predicate. - * - * @param {(value: T) => boolean} predicate - * @returns {number} - */ -function indexOfFirst(this: Sequence, predicate: (value: T) => boolean): number { - let index = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { - return index; +export class IndexOfFirst { + + /** + * Returns the zero-based index of the first element matching the given `predicate` or -1 if no element matches + * the predicate. + * + * @param {(value: T) => boolean} predicate + * @returns {number} + */ + indexOfFirst(this: Sequence, predicate: (value: T) => boolean): number { + let index = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + return index; + } + index++; } - index++; + return -1; } - return -1; -} -export default indexOfFirst; \ No newline at end of file +} diff --git a/src/indexOfLast.ts b/src/indexOfLast.ts index 295a2b1..608ca48 100644 --- a/src/indexOfLast.ts +++ b/src/indexOfLast.ts @@ -1,23 +1,25 @@ import Sequence from "./Sequence"; -/** - * Returns the zero-based index of the last element matching the given `predicate` or -1 if no element matches - * the predicate. - * - * @param {(value: T) => boolean} predicate - * @returns {number} - */ -function indexOfLast(this: Sequence, predicate: (value: T) => boolean): number { - let index = 0; - let result = -1; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { - result = index; +export class IndexOfLast { + + /** + * Returns the zero-based index of the last element matching the given `predicate` or -1 if no element matches + * the predicate. + * + * @param {(value: T) => boolean} predicate + * @returns {number} + */ + indexOfLast(this: Sequence, predicate: (value: T) => boolean): number { + let index = 0; + let result = -1; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + result = index; + } + index++; } - index++; + return result; } - return result; -} -export default indexOfLast; \ No newline at end of file +} \ No newline at end of file diff --git a/src/joinToString.ts b/src/joinToString.ts index e0d1a4a..7a62ac3 100644 --- a/src/joinToString.ts +++ b/src/joinToString.ts @@ -20,47 +20,59 @@ const defaults = { transform: undefined }; -/** - * Joins all elements of the sequence into a string with the given configuration. - * - * @param {JoinConfig} config - * @returns {string} - */ -function joinToString(this: Sequence, config: JoinConfig = defaults): string { - const { - value = defaults.value, - separator = defaults.separator, - prefix = defaults.prefix, - postfix = defaults.postfix, - limit = defaults.limit, - truncated = defaults.truncated, - transform = defaults.transform - } = config; +export class JoinToString { - let result = `${value}${prefix}`; - let count = 0; + /** + * Joins all elements of the sequence into a string with the given configuration. + * + * @param {JoinConfig} config + * @returns {string} + */ + joinToString(this: Sequence, config: JoinConfig = defaults): string { + const { + value = defaults.value, + separator = defaults.separator, + prefix = defaults.prefix, + postfix = defaults.postfix, + limit = defaults.limit, + truncated = defaults.truncated, + transform = defaults.transform + } = config; - while (this.iterator.hasNext()) { - count++; - const item = this.iterator.next(); - if (count > 1) { - result += separator; + let result = `${value}${prefix}`; + let count = 0; + + while (this.iterator.hasNext()) { + count++; + const item = this.iterator.next(); + if (count > 1) { + result += separator; + } + if (limit < 0 || count <= limit) { + result += transform != null + ? transform(item) + : String(item); + } else { + break; + } } - if (limit < 0 || count <= limit) { - result += transform != null - ? transform(item) - : String(item); - } else { - break; + + if (limit >= 0 && count > limit) { + result += truncated; } - } - if (limit >= 0 && count > limit) { - result += truncated; + result += postfix; + return result; } - result += postfix; - return result; -} + /** + * Joins all elements of the sequence into a string with the given configuration. + * + * @param {JoinConfig} config + * @returns {string} + */ + joinTo(this: Sequence, config: JoinConfig = defaults): string { + return this.joinToString(config); + } -export default joinToString; \ No newline at end of file +} \ No newline at end of file diff --git a/src/last.ts b/src/last.ts index 56536e0..587318d 100644 --- a/src/last.ts +++ b/src/last.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise throws - * an error. - * - * @param {(value: T) => boolean} predicate - * @returns {T} - */ -function last(this: Sequence, predicate?: (value: T) => boolean): T { - if (predicate != null) { - return this.filter(predicate).last(); - } - if (!this.iterator.hasNext()) { - throw new Error("No such element"); - } - let item: T; - while (this.iterator.hasNext()) { - item = this.iterator.next(); +export class Last { + + /** + * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise throws + * an error. + * + * @param {(value: T) => boolean} predicate + * @returns {T} + */ + last(this: Sequence, predicate?: (value: T) => boolean): T { + if (predicate != null) { + return this.filter(predicate).last(); + } + if (!this.iterator.hasNext()) { + throw new Error("No such element"); + } + let item: T; + while (this.iterator.hasNext()) { + item = this.iterator.next(); + } + return item!; } - return item!; -} -export default last; \ No newline at end of file +} \ No newline at end of file diff --git a/src/lastOrNull.ts b/src/lastOrNull.ts index ebef7a2..5667ffc 100644 --- a/src/lastOrNull.ts +++ b/src/lastOrNull.ts @@ -1,20 +1,32 @@ import Sequence from "./Sequence"; -/** - * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise returns `null`. - * - * @param {(value: T) => boolean} predicate - * @returns {T} - */ -function lastOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { - if (predicate != null) { - return this.filter(predicate).lastOrNull(); +export class LastOrNull { + + /** + * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise returns `null`. + * + * @param {(value: T) => boolean} predicate + * @returns {T} + */ + lastOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { + if (predicate != null) { + return this.filter(predicate).lastOrNull(); + } + let item: T | null = null; + while (this.iterator.hasNext()) { + item = this.iterator.next(); + } + return item; } - let item: T | null = null; - while (this.iterator.hasNext()) { - item = this.iterator.next(); + + /** + * Returns the last element of the sequence or the last element matching `predicate` if present, otherwise returns `null`. + * + * @param {(value: T) => boolean} predicate + * @returns {T} + */ + findLast(this: Sequence, predicate?: (value: T) => boolean): T | null { + return this.lastOrNull(predicate); } - return item; -} -export default lastOrNull; \ No newline at end of file +} \ No newline at end of file diff --git a/src/map.ts b/src/map.ts index ff6fc06..7dba869 100644 --- a/src/map.ts +++ b/src/map.ts @@ -1,5 +1,5 @@ import SequenceIterator from "./SequenceIterator"; -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; class MapIterator implements SequenceIterator { constructor( @@ -17,14 +17,16 @@ class MapIterator implements SequenceIterator { } } -/** - * Transforms each element into another value by applying the given `transform` function and returns a new sequence. - * - * @param {(T) => S} transform - * @returns {Sequence} - */ -function map(this: Sequence, transform: (element: T) => S): Sequence { - return new Sequence(new MapIterator(transform, this.iterator)); -} +export class Map { + + /** + * Transforms each element into another value by applying the given `transform` function and returns a new sequence. + * + * @param {(T) => S} transform + * @returns {Sequence} + */ + map(this: Sequence, transform: (element: T) => S): Sequence { + return createSequence(new MapIterator(transform, this.iterator)); + } -export default map; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mapIndexed.ts b/src/mapIndexed.ts index b233527..9cede3b 100644 --- a/src/mapIndexed.ts +++ b/src/mapIndexed.ts @@ -1,14 +1,16 @@ import Sequence from "./Sequence"; -/** - * Transforms each element into another value by applying the given `transform` function and returns a new sequence. - * - * @param {(index: number, value: T) => R} transform - * @returns {Sequence} - */ -function mapIndexed(this: Sequence, transform: (index: number, value: T) => R): Sequence { - return this.withIndex() - .map(it => transform(it.index, it.value)); -} +export class MapIndexed { -export default mapIndexed; \ No newline at end of file + /** + * Transforms each element into another value by applying the given `transform` function and returns a new sequence. + * + * @param {(index: number, value: T) => R} transform + * @returns {Sequence} + */ + mapIndexed(this: Sequence, transform: (index: number, value: T) => R): Sequence { + return this.withIndex() + .map(it => transform(it.index, it.value)); + } + +} \ No newline at end of file diff --git a/src/mapNotNull.ts b/src/mapNotNull.ts index bdea28c..cfd27ce 100644 --- a/src/mapNotNull.ts +++ b/src/mapNotNull.ts @@ -1,19 +1,21 @@ import Sequence, {emptySequence, sequenceOf} from "./Sequence"; -/** - * Transforms each element into another value by applying the given `transform` function and returns a new sequence. - * Transformations into `null` values are discarded. - * - * @param {(value: T) => R} transform - * @returns {Sequence} - */ -function mapNotNull(this: Sequence, transform: (value: T) => R | null): Sequence { - return this.flatMap((value: T) => { - const item = transform(value); - return item !== null - ? sequenceOf(item) - : emptySequence(); - }); -} +export class MapNotNull { -export default mapNotNull; \ No newline at end of file + /** + * Transforms each element into another value by applying the given `transform` function and returns a new sequence. + * Transformations into `null` values are discarded. + * + * @param {(value: T) => R} transform + * @returns {Sequence} + */ + mapNotNull(this: Sequence, transform: (value: T) => R | null): Sequence { + return this.flatMap((value: T) => { + const item = transform(value); + return item !== null + ? sequenceOf(item) + : emptySequence(); + }); + } + +} \ No newline at end of file diff --git a/src/max.ts b/src/max.ts index 184b89a..2136b99 100644 --- a/src/max.ts +++ b/src/max.ts @@ -1,19 +1,21 @@ import Sequence from "./Sequence"; -/** - * Returns the maximum element of the sequence or `null` if sequence is empty. - * - * @returns {T} - */ -function max(this: Sequence): T | null { - let result: T | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (result == null || item > result) { - result = item; +export class Max { + + /** + * Returns the maximum element of the sequence or `null` if sequence is empty. + * + * @returns {T} + */ + max(this: Sequence): T | null { + let result: T | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (result == null || item > result) { + result = item; + } } + return result; } - return result; -} -export default max; \ No newline at end of file +} \ No newline at end of file diff --git a/src/maxBy.ts b/src/maxBy.ts index 65b0f43..34a027e 100644 --- a/src/maxBy.ts +++ b/src/maxBy.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Returns the maximum element by comparing the results of the given `selector` function - * for each element of the sequence or `null` if the sequence is empty. - * - * @param {(value: T) => R} selector - * @returns {T} - */ -function maxBy(this: Sequence, selector: (value: T) => R): T | null { - let max: T | null = null; - let maxSelected: R | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const value = selector(item); - if (maxSelected == null || value > maxSelected) { - maxSelected = value; - max = item; +export class MaxBy { + + /** + * Returns the maximum element by comparing the results of the given `selector` function + * for each element of the sequence or `null` if the sequence is empty. + * + * @param {(value: T) => R} selector + * @returns {T} + */ + maxBy(this: Sequence, selector: (value: T) => R): T | null { + let max: T | null = null; + let maxSelected: R | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const value = selector(item); + if (maxSelected == null || value > maxSelected) { + maxSelected = value; + max = item; + } } + return max; } - return max; -} -export default maxBy; \ No newline at end of file +} \ No newline at end of file diff --git a/src/maxWith.ts b/src/maxWith.ts index db673fa..00d960b 100644 --- a/src/maxWith.ts +++ b/src/maxWith.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Returns the maximum element of the sequence by evaluating the given `compare` - * function or `null` if sequence is empty. - * - * @returns {T} - */ -function maxWith(this: Sequence, compare: (a: T, b: T) => number): T | null { - let max: T | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (max == null || compare(item, max) > 0) { - max = item; +export class MaxWith { + + /** + * Returns the maximum element of the sequence by evaluating the given `compare` + * function or `null` if sequence is empty. + * + * @returns {T} + */ + maxWith(this: Sequence, compare: (a: T, b: T) => number): T | null { + let max: T | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (max == null || compare(item, max) > 0) { + max = item; + } } + return max; } - return max; -} -export default maxWith; \ No newline at end of file +} \ No newline at end of file diff --git a/src/merge.ts b/src/merge.ts index ee9087d..c0a3053 100644 --- a/src/merge.ts +++ b/src/merge.ts @@ -1,37 +1,39 @@ -import Sequence, {asSequence} from "./Sequence"; +import Sequence, {asSequence, isSequence} from "./Sequence"; -/** - * Merges the elements of both sequences into a new sequence. Each element of this sequence is eventually replaced with - * an element of the other sequence by comparing results of the given `selector` function. If no value is found in the other - * sequence the element is retained. New elements of the other sequence are appended to the end of the new sequence or - * prepended to the start of the new sequence, if `prependNewValues` is set to `true`. - * - * @param {Sequence} other - * @param {(value: T) => S} selector - * @param prependNewValues - * @returns {Sequence} - */ -function merge(this: Sequence, other: Sequence | Iterable, selector: (value: T) => S, prependNewValues: boolean = false): Sequence { - let mergeValues = other instanceof Sequence - ? other.toArray() - : asSequence(other).toArray(); - const leftValues = this.toArray(); - const result = leftValues.map(left => { - const selected = selector(left); - const right = asSequence(mergeValues) - .find(it => selector(it) === selected); - if (right != null) { - mergeValues = mergeValues.filter(it => it !== right); - return right; +export class Merge { + + /** + * Merges the elements of both sequences into a new sequence. Each element of this sequence is eventually replaced with + * an element of the other sequence by comparing results of the given `selector` function. If no value is found in the other + * sequence the element is retained. New elements of the other sequence are appended to the end of the new sequence or + * prepended to the start of the new sequence, if `prependNewValues` is set to `true`. + * + * @param {Sequence} other + * @param {(value: T) => S} selector + * @param prependNewValues + * @returns {Sequence} + */ + merge(this: Sequence, other: Sequence | Iterable, selector: (value: T) => S, prependNewValues: boolean = false): Sequence { + let mergeValues = isSequence(other) + ? other.toArray() + : asSequence(other).toArray(); + const leftValues = this.toArray(); + const result = leftValues.map(left => { + const selected = selector(left); + const right = asSequence(mergeValues) + .find(it => selector(it) === selected); + if (right != null) { + mergeValues = mergeValues.filter(it => it !== right); + return right; + } else { + return left; + } + }); + if (prependNewValues) { + return asSequence([...mergeValues, ...result]); } else { - return left; + return asSequence([...result, ...mergeValues]); } - }); - if (prependNewValues) { - return asSequence([...mergeValues, ...result]); - } else { - return asSequence([...result, ...mergeValues]); } -} -export default merge; \ No newline at end of file +} \ No newline at end of file diff --git a/src/min.ts b/src/min.ts index 0cc193e..234f9f1 100644 --- a/src/min.ts +++ b/src/min.ts @@ -1,19 +1,21 @@ import Sequence from "./Sequence"; -/** - * Returns the minimum element of the sequence or `null` if sequence is empty. - * - * @returns {T} - */ -function min(this: Sequence): T | null { - let result: T | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (result == null || item < result) { - result = item; +export class Min { + + /** + * Returns the minimum element of the sequence or `null` if sequence is empty. + * + * @returns {T} + */ + min(this: Sequence): T | null { + let result: T | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (result == null || item < result) { + result = item; + } } + return result; } - return result; -} -export default min; \ No newline at end of file +} \ No newline at end of file diff --git a/src/minBy.ts b/src/minBy.ts index b495726..921ac09 100644 --- a/src/minBy.ts +++ b/src/minBy.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Returns the minimum element by comparing the results of the given `selector` function - * for each element of the sequence or `null` if the sequence is empty. - * - * @param {(value: T) => R} selector - * @returns {T} - */ -function minBy(this: Sequence, selector: (value: T) => R): T | null { - let min: T | null = null; - let minSelected: R | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - const value = selector(item); - if (minSelected == null || value < minSelected) { - minSelected = value; - min = item; +export class MinBy { + + /** + * Returns the minimum element by comparing the results of the given `selector` function + * for each element of the sequence or `null` if the sequence is empty. + * + * @param {(value: T) => R} selector + * @returns {T} + */ + minBy(this: Sequence, selector: (value: T) => R): T | null { + let min: T | null = null; + let minSelected: R | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + const value = selector(item); + if (minSelected == null || value < minSelected) { + minSelected = value; + min = item; + } } + return min; } - return min; -} -export default minBy; \ No newline at end of file +} \ No newline at end of file diff --git a/src/minWith.ts b/src/minWith.ts index 5e9fe10..d554188 100644 --- a/src/minWith.ts +++ b/src/minWith.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Returns the minimum element of the sequence by evaluating the given `compare` - * function or `null` if sequence is empty. - * - * @returns {T} - */ -function minWith(this: Sequence, compare: (a: T, b: T) => number): T | null { - let min: T | null = null; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (min == null || compare(item, min) < 0) { - min = item; +export class MinWith { + + /** + * Returns the minimum element of the sequence by evaluating the given `compare` + * function or `null` if sequence is empty. + * + * @returns {T} + */ + minWith(this: Sequence, compare: (a: T, b: T) => number): T | null { + let min: T | null = null; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (min == null || compare(item, min) < 0) { + min = item; + } } + return min; } - return min; -} -export default minWith; \ No newline at end of file +} \ No newline at end of file diff --git a/src/minus.ts b/src/minus.ts index 4a985de..9fefebe 100644 --- a/src/minus.ts +++ b/src/minus.ts @@ -1,21 +1,23 @@ -import Sequence from "./Sequence"; +import Sequence, {isSequence} from "./Sequence"; -/** - * Removes the given `data` and returns a new sequence. Data can either be a single element, an array of elements - * or a sequence of elements. - * - * @param {Sequence | Array | T} data - * @returns {Sequence} - */ -function minus(this: Sequence, data: T | Sequence | Array): Sequence { - if (data instanceof Sequence) { - const array: Array = data.toArray(); - return this.filter(it => array.indexOf(it) < 0); - } else if (data instanceof Array) { - return this.filter(it => data.indexOf(it) < 0); - } else { - return this.filter(it => it !== data); +export class Minus { + + /** + * Removes the given `data` and returns a new sequence. Data can either be a single element, an array of elements + * or a sequence of elements. + * + * @param {Sequence | Array | T} data + * @returns {Sequence} + */ + minus(this: Sequence, data: T | Sequence | Array): Sequence { + if (isSequence(data)) { + const array: Array = data.toArray(); + return this.filter(it => array.indexOf(it) < 0); + } else if (data instanceof Array) { + return this.filter(it => data.indexOf(it) < 0); + } else { + return this.filter(it => it !== data); + } } -} -export default minus; \ No newline at end of file +} \ No newline at end of file diff --git a/src/none.ts b/src/none.ts index 7cbe22e..1abf924 100644 --- a/src/none.ts +++ b/src/none.ts @@ -1,23 +1,25 @@ import Sequence from "./Sequence"; -/** - * Returns `true` if no element match the given `predicate` or if the sequence is empty - * if no predicate is present. - * - * @param {(value: T) => boolean} predicate - * @returns {boolean} - */ -function none(this: Sequence, predicate?: (value: T) => boolean): boolean { - if (predicate == null) { - return !this.iterator.hasNext(); - } - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { - return false; +export class None { + + /** + * Returns `true` if no element match the given `predicate` or if the sequence is empty + * if no predicate is present. + * + * @param {(value: T) => boolean} predicate + * @returns {boolean} + */ + none(this: Sequence, predicate?: (value: T) => boolean): boolean { + if (predicate == null) { + return !this.iterator.hasNext(); + } + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + return false; + } } + return true; } - return true; -} -export default none; \ No newline at end of file +} \ No newline at end of file diff --git a/src/onEach.ts b/src/onEach.ts index c9eaee8..3fc3645 100644 --- a/src/onEach.ts +++ b/src/onEach.ts @@ -1,16 +1,18 @@ import Sequence from "./Sequence"; -/** - * Performs the given `action` for each element and returns the sequence. - * - * @param {(value: T) => void} action - * @returns {Sequence} - */ -function onEach(this: Sequence, action: (value: T) => void): Sequence { - return this.map(it => { - action(it); - return it; - }); -} +export class OnEach { -export default onEach; \ No newline at end of file + /** + * Performs the given `action` for each element and returns the sequence. + * + * @param {(value: T) => void} action + * @returns {Sequence} + */ + onEach(this: Sequence, action: (value: T) => void): Sequence { + return this.map(it => { + action(it); + return it; + }); + } + +} \ No newline at end of file diff --git a/src/partition.ts b/src/partition.ts index 8f38835..72f42bc 100644 --- a/src/partition.ts +++ b/src/partition.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Evaluates the given `predicate` for each element of the sequence and assorts each element into one of two lists - * according to the result of the predicate. Returns both lists as an object. - * - * @param {(value: T) => boolean} predicate - * @returns {{true: Array; false: Array}} - */ -function partition(this: Sequence, predicate: (value: T) => boolean): { "true": Array, "false": Array } { - const arrayTrue: Array = []; - const arrayFalse: Array = []; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - if (predicate(item)) { - arrayTrue.push(item); - } else { - arrayFalse.push(item); +export class Partition { + + /** + * Evaluates the given `predicate` for each element of the sequence and assorts each element into one of two lists + * according to the result of the predicate. Returns both lists as an object. + * + * @param {(value: T) => boolean} predicate + * @returns {{true: Array; false: Array}} + */ + partition(this: Sequence, predicate: (value: T) => boolean): { "true": Array, "false": Array } { + const arrayTrue: Array = []; + const arrayFalse: Array = []; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + if (predicate(item)) { + arrayTrue.push(item); + } else { + arrayFalse.push(item); + } } + return { "true": arrayTrue, "false": arrayFalse }; } - return {"true": arrayTrue, "false": arrayFalse}; -} -export default partition; \ No newline at end of file +} \ No newline at end of file diff --git a/src/plus.ts b/src/plus.ts index 1306baa..372f53f 100644 --- a/src/plus.ts +++ b/src/plus.ts @@ -1,11 +1,10 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence, isSequence} from "./Sequence"; import SequenceIterator, {IterableIterator} from "./SequenceIterator"; class AppendIterator implements SequenceIterator { - constructor( - private readonly first: SequenceIterator, - private readonly second: SequenceIterator - ) {} + constructor(private readonly first: SequenceIterator, + private readonly second: SequenceIterator) { + } hasNext(): boolean { return this.first.hasNext() || this.second.hasNext(); @@ -19,21 +18,40 @@ class AppendIterator implements SequenceIterator { } -/** - * Appends the given `data` to the end of the sequence and returns a new sequence. Data can either be a single element, - * an array of elements or a sequence of elements. - * - * @param {Sequence | Array | T} data - * @returns {Sequence} - */ -function plus(this: Sequence, data: T | Sequence | Array): Sequence { - if (data instanceof Sequence) { - return new Sequence(new AppendIterator(this.iterator, data.iterator)); - } else if (data instanceof Array) { - return new Sequence(new AppendIterator(this.iterator, new IterableIterator(data))); - } else { - return new Sequence(new AppendIterator(this.iterator, new IterableIterator([data]))); +export class Plus { + + /** + * Appends the given `element` to the end of the sequence and returns a new sequence. + * + * @param {T} element + * @returns {Sequence} + */ + plus(this: Sequence, element: T): Sequence; + + /** + * Appends the given array to the end of the sequence and returns a new sequence. + * + * @param {Array} other + * @returns {Sequence} + */ + plus(this: Sequence, other: Array): Sequence; + + /** + * Appends the given sequence to the end of the sequence and returns a new sequence. + * + * @param {Sequence} other + * @returns {Sequence} + */ + plus(this: Sequence, other: Sequence): Sequence; + + plus(this: Sequence, data: T | Sequence | Array): Sequence { + if (isSequence(data)) { + return createSequence(new AppendIterator(this.iterator, data.iterator)); + } else if (data instanceof Array) { + return createSequence(new AppendIterator(this.iterator, new IterableIterator(data))); + } else { + return createSequence(new AppendIterator(this.iterator, new IterableIterator([data]))); + } } -} -export default plus; \ No newline at end of file +} \ No newline at end of file diff --git a/src/reduce.ts b/src/reduce.ts index 957389a..7798610 100644 --- a/src/reduce.ts +++ b/src/reduce.ts @@ -1,24 +1,26 @@ import Sequence from "./Sequence"; -/** - * Reduces the whole sequence to a single value by invoking `operation` with each element - * from left to right. For every invocation of the operation `acc` is the result of the last - * invocation. For the first invocation of the operation `acc` is the first element of the - * sequence. - * - * @param {(acc: S, value: T) => S} operation - * @returns {S} - */ -function reduce(this: Sequence, operation: (acc: S, value: T) => S): S { - if (!this.iterator.hasNext()) { - throw new Error("Cannot reduce empty sequence"); - } - let result: S = this.iterator.next(); - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result = operation(result, item); +export class Reduce { + + /** + * Reduces the whole sequence to a single value by invoking `operation` with each element + * from left to right. For every invocation of the operation `acc` is the result of the last + * invocation. For the first invocation of the operation `acc` is the first element of the + * sequence. + * + * @param {(acc: S, value: T) => S} operation + * @returns {S} + */ + reduce(this: Sequence, operation: (acc: S, value: T) => S): S { + if (!this.iterator.hasNext()) { + throw new Error("Cannot reduce empty sequence"); + } + let result: S = this.iterator.next(); + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result = operation(result, item); + } + return result; } - return result; -} -export default reduce; \ No newline at end of file +} \ No newline at end of file diff --git a/src/reduceIndexed.ts b/src/reduceIndexed.ts index 12e820b..3f14094 100644 --- a/src/reduceIndexed.ts +++ b/src/reduceIndexed.ts @@ -1,26 +1,28 @@ import Sequence from "./Sequence"; -/** - * Reduces the whole sequence to a single value by invoking `operation` with each element - * from left to right. For every invocation of the operation `acc` is the result of the last - * invocation. For the first invocation of the operation `acc` is the first element of the - * sequence. In addition the `index` of the current element is also passed to the operation. - * - * @param {(index: number, acc: S, element: T) => S} operation - * @returns {S} - */ -function reduceIndexed(this: Sequence, operation: (index: number, acc: S, element: T) => S): S { - if (!this.iterator.hasNext()) { - throw new Error("Cannot reduce empty sequence"); - } - let index = 1; - let result: S = this.iterator.next(); - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result = operation(index, result, item); - index++; +export class ReduceIndexed { + + /** + * Reduces the whole sequence to a single value by invoking `operation` with each element + * from left to right. For every invocation of the operation `acc` is the result of the last + * invocation. For the first invocation of the operation `acc` is the first element of the + * sequence. In addition the `index` of the current element is also passed to the operation. + * + * @param {(index: number, acc: S, element: T) => S} operation + * @returns {S} + */ + reduceIndexed(this: Sequence, operation: (index: number, acc: S, element: T) => S): S { + if (!this.iterator.hasNext()) { + throw new Error("Cannot reduce empty sequence"); + } + let index = 1; + let result: S = this.iterator.next(); + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result = operation(index, result, item); + index++; + } + return result; } - return result; -} -export default reduceIndexed; \ No newline at end of file +} \ No newline at end of file diff --git a/src/reverse.ts b/src/reverse.ts index 031c909..d2633cf 100644 --- a/src/reverse.ts +++ b/src/reverse.ts @@ -1,14 +1,16 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence with all elements of the sequence in reverse order. - * - * @returns {Sequence} - */ -function reverse(this: Sequence): Sequence { - return this.withIndex() - .sortedByDescending(it => it.index) - .map(it => it.value); -} +export class Reverse { -export default reverse; \ No newline at end of file + /** + * Returns a new sequence with all elements of the sequence in reverse order. + * + * @returns {Sequence} + */ + reverse(this: Sequence): Sequence { + return this.withIndex() + .sortedByDescending(it => it.index) + .map(it => it.value); + } + +} \ No newline at end of file diff --git a/src/single.ts b/src/single.ts index 5c25fe8..f1d7a81 100644 --- a/src/single.ts +++ b/src/single.ts @@ -1,25 +1,27 @@ import Sequence from "./Sequence"; -/** - * Returns the single element of the sequence or throws error if the sequence has more than - * one element or none at all. If a `predicate` is passed returns the single element matching - * the predicate or throws an error if more or less than one element match the predicate. - * - * @param {(value: T) => boolean} predicate - * @returns {T} - */ -function single(this: Sequence, predicate?: (value: T) => boolean): T { - if (predicate != null) { - return this.filter(predicate).single(); - } - if (!this.iterator.hasNext()) { - throw new Error("No such element"); - } - const item = this.iterator.next(); - if (this.iterator.hasNext()) { - throw new Error("Expect single element"); +export class Single { + + /** + * Returns the single element of the sequence or throws error if the sequence has more than + * one element or none at all. If a `predicate` is passed returns the single element matching + * the predicate or throws an error if more or less than one element match the predicate. + * + * @param {(value: T) => boolean} predicate + * @returns {T} + */ + single(this: Sequence, predicate?: (value: T) => boolean): T { + if (predicate != null) { + return this.filter(predicate).single(); + } + if (!this.iterator.hasNext()) { + throw new Error("No such element"); + } + const item = this.iterator.next(); + if (this.iterator.hasNext()) { + throw new Error("Expect single element"); + } + return item; } - return item; -} -export default single; \ No newline at end of file +} \ No newline at end of file diff --git a/src/singleOrNull.ts b/src/singleOrNull.ts index df27ef0..8304895 100644 --- a/src/singleOrNull.ts +++ b/src/singleOrNull.ts @@ -1,25 +1,27 @@ import Sequence from "./Sequence"; -/** - * Returns the single element of the sequence or `null` if the sequence has more than - * one element or none at all. If a `predicate` is passed returns the single element matching - * the predicate or `null` if more or less than one element match the predicate. - * - * @param {(value: T) => boolean} predicate - * @returns {T} - */ -function singleOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { - if (predicate != null) { - return this.filter(predicate).singleOrNull(); - } - if (!this.iterator.hasNext()) { - return null; - } - const item = this.iterator.next(); - if (this.iterator.hasNext()) { - return null; +export class SingleOrNull { + + /** + * Returns the single element of the sequence or `null` if the sequence has more than + * one element or none at all. If a `predicate` is passed returns the single element matching + * the predicate or `null` if more or less than one element match the predicate. + * + * @param {(value: T) => boolean} predicate + * @returns {T} + */ + singleOrNull(this: Sequence, predicate?: (value: T) => boolean): T | null { + if (predicate != null) { + return this.filter(predicate).singleOrNull(); + } + if (!this.iterator.hasNext()) { + return null; + } + const item = this.iterator.next(); + if (this.iterator.hasNext()) { + return null; + } + return item; } - return item; -} -export default singleOrNull; \ No newline at end of file +} \ No newline at end of file diff --git a/src/sorted.ts b/src/sorted.ts index f86a60c..1670232 100644 --- a/src/sorted.ts +++ b/src/sorted.ts @@ -1,29 +1,31 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import {IterableIterator} from "./SequenceIterator"; import ComparatorFactory from "./ComparatorFactory"; import Comparator from "./Comparator"; import createComparatorFactory from "./createComparatorFactory"; -/** - * Returns a new sequence with all elements sorted by the comparator specified by the given `composeComparator` function - * or in natural order if no arguments are given. - * - * @returns {Sequence} - */ -function sorted(this: Sequence, composeComparator?: (factory: ComparatorFactory) => Comparator): Sequence { - const result: Array = []; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result.push(item); - } - if (composeComparator == null) { - result.sort(); - } else { - const factory: ComparatorFactory = createComparatorFactory(); - const comparator = composeComparator(factory); - result.sort(comparator); +export class Sorted { + + /** + * Returns a new sequence with all elements sorted by the comparator specified by the given `composeComparator` function + * or in natural order if no arguments are given. + * + * @returns {Sequence} + */ + sorted(this: Sequence, composeComparator?: (factory: ComparatorFactory) => Comparator): Sequence { + const result: Array = []; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result.push(item); + } + if (composeComparator == null) { + result.sort(); + } else { + const factory: ComparatorFactory = createComparatorFactory(); + const comparator = composeComparator(factory); + result.sort(comparator); + } + return createSequence(new IterableIterator(result)); } - return new Sequence(new IterableIterator(result)); -} -export default sorted; \ No newline at end of file +} \ No newline at end of file diff --git a/src/sortedBy.ts b/src/sortedBy.ts index d63be23..26412a2 100644 --- a/src/sortedBy.ts +++ b/src/sortedBy.ts @@ -1,14 +1,16 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence with all elements sorted ascending by the value specified - * by the given `selector` function. - * - * @param {(value: T) => R} selector - * @returns {Sequence} - */ -function sortedBy(this: Sequence, selector: (value: T) => R): Sequence { - return this.sorted(it => it.compareBy(selector)); -} +export class SortedBy { -export default sortedBy; \ No newline at end of file + /** + * Returns a new sequence with all elements sorted ascending by the value specified + * by the given `selector` function. + * + * @param {(value: T) => R} selector + * @returns {Sequence} + */ + sortedBy(this: Sequence, selector: (value: T) => R): Sequence { + return this.sorted(it => it.compareBy(selector)); + } + +} \ No newline at end of file diff --git a/src/sortedByDescending.ts b/src/sortedByDescending.ts index f3f7091..1a03761 100644 --- a/src/sortedByDescending.ts +++ b/src/sortedByDescending.ts @@ -1,14 +1,16 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence with all elements sorted descending by the value specified - * by the given `selector` function. - * - * @param {(value: T) => R} selector - * @returns {Sequence} - */ -function sortedByDescending(this: Sequence, selector: (value: T) => R): Sequence { - return this.sorted(it => it.compareByDescending(selector)); -} +export class SortedByDescending { -export default sortedByDescending; \ No newline at end of file + /** + * Returns a new sequence with all elements sorted descending by the value specified + * by the given `selector` function. + * + * @param {(value: T) => R} selector + * @returns {Sequence} + */ + sortedByDescending(this: Sequence, selector: (value: T) => R): Sequence { + return this.sorted(it => it.compareByDescending(selector)); + } + +} \ No newline at end of file diff --git a/src/sortedDescending.ts b/src/sortedDescending.ts index b27f084..104d22f 100644 --- a/src/sortedDescending.ts +++ b/src/sortedDescending.ts @@ -1,12 +1,14 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence with all elements sorted in reverse (descending) natural order. - * - * @returns {Sequence} - */ -function sortedDescending(this: Sequence): Sequence { - return this.sorted(it => it.reverseOrder()); -} +export class SortedDescending { -export default sortedDescending; \ No newline at end of file + /** + * Returns a new sequence with all elements sorted in reverse (descending) natural order. + * + * @returns {Sequence} + */ + sortedDescending(this: Sequence): Sequence { + return this.sorted(it => it.reverseOrder()); + } + +} \ No newline at end of file diff --git a/src/sortedWith.ts b/src/sortedWith.ts index 0e85a31..34a74a6 100644 --- a/src/sortedWith.ts +++ b/src/sortedWith.ts @@ -1,14 +1,15 @@ import Sequence from "./Sequence"; -import {IterableIterator} from "./SequenceIterator"; -/** - * Returns a new sequence with all elements sorted be the given `compare` function. - * - * @param {(a: T, b: T) => number} comparison - * @returns {Sequence} - */ -function sortedWith(this: Sequence, comparison: (a: T, b: T) => number): Sequence { - return this.sorted(it => it.compare(comparison)); -} +export class SortedWith { -export default sortedWith; \ No newline at end of file + /** + * Returns a new sequence with all elements sorted be the given `compare` function. + * + * @param {(a: T, b: T) => number} comparison + * @returns {Sequence} + */ + sortedWith(this: Sequence, comparison: (a: T, b: T) => number): Sequence { + return this.sorted(it => it.compare(comparison)); + } + +} \ No newline at end of file diff --git a/src/sum.ts b/src/sum.ts index 11c444b..2508053 100644 --- a/src/sum.ts +++ b/src/sum.ts @@ -1,16 +1,18 @@ import Sequence from "./Sequence"; -/** - * Returns the sum of all numbers. - * - * @returns {number} - */ -function sum(this: Sequence): number { - let result = 0; - while (this.iterator.hasNext()) { - result += this.iterator.next(); +export class Sum { + + /** + * Returns the sum of all numbers. + * + * @returns {number} + */ + sum(this: Sequence): number { + let result = 0; + while (this.iterator.hasNext()) { + result += this.iterator.next(); + } + return result; } - return result; -} -export default sum; \ No newline at end of file +} \ No newline at end of file diff --git a/src/sumBy.ts b/src/sumBy.ts index 6fdb2d4..ea2184d 100644 --- a/src/sumBy.ts +++ b/src/sumBy.ts @@ -1,18 +1,20 @@ import Sequence from "./Sequence"; -/** - * Returns the sum of all numbers specified by the given `selector` function. - * - * @param {(value: T) => number} selector - * @returns {number} - */ -function sumBy(this: Sequence, selector: (value: T) => number): number { - let result = 0; - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result += selector(item); +export class SumBy { + + /** + * Returns the sum of all numbers specified by the given `selector` function. + * + * @param {(value: T) => number} selector + * @returns {number} + */ + sumBy(this: Sequence, selector: (value: T) => number): number { + let result = 0; + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result += selector(item); + } + return result; } - return result; -} -export default sumBy; \ No newline at end of file +} \ No newline at end of file diff --git a/src/take.ts b/src/take.ts index e734545..06fc68c 100644 --- a/src/take.ts +++ b/src/take.ts @@ -1,14 +1,18 @@ import Sequence from "./Sequence"; -/** - * Returns a new sequence consisting of the first `n` elements. All other elements - * are discarded. - * - * @param {number} n - * @returns {Sequence} - */ -export default function take(this: Sequence, n: number): Sequence { - return this.withIndex() - .takeWhile(it => it.index < n) - .map(it => it.value); +export class Take { + + /** + * Returns a new sequence consisting of the first `n` elements. All other elements + * are discarded. + * + * @param {number} n + * @returns {Sequence} + */ + take(this: Sequence, n: number): Sequence { + return this.withIndex() + .takeWhile(it => it.index < n) + .map(it => it.value); + } + } \ No newline at end of file diff --git a/src/takeWhile.ts b/src/takeWhile.ts index 3a870d6..c86ba38 100644 --- a/src/takeWhile.ts +++ b/src/takeWhile.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import SequenceIterator from "./SequenceIterator"; class TakeWhileIterator implements SequenceIterator { @@ -38,14 +38,16 @@ class TakeWhileIterator implements SequenceIterator { } } -/** - * Takes all elements of the sequence as long as the given `predicate` evaluates to true. - * - * @param {(item: T) => boolean} predicate - * @returns {Sequence} - */ -function takeWhile(this: Sequence, predicate: (item: T) => boolean): Sequence { - return new Sequence(new TakeWhileIterator(this.iterator, predicate)); -} +export class TakeWhile { + + /** + * Takes all elements of the sequence as long as the given `predicate` evaluates to true. + * + * @param {(item: T) => boolean} predicate + * @returns {Sequence} + */ + takeWhile(this: Sequence, predicate: (item: T) => boolean): Sequence { + return createSequence(new TakeWhileIterator(this.iterator, predicate)); + } -export default takeWhile; \ No newline at end of file +} \ No newline at end of file diff --git a/src/toArray.ts b/src/toArray.ts index 9a59610..c2daab6 100644 --- a/src/toArray.ts +++ b/src/toArray.ts @@ -1,18 +1,31 @@ import Sequence from "./Sequence"; -/** - * Returns all elements of the sequence as array. If an `array` is passed - * the elements are appended to the end of the array. - * - * @param {Array} array - * @returns {Array} - */ -function toArray(this: Sequence, array?: Array): Array { - const result: Array = array || []; - while (this.iterator.hasNext()) { - result.push(this.iterator.next()); +export class ToArray { + + /** + * Returns all elements of the sequence as array. If an `array` is passed + * the elements are appended to the end of the array. + * + * @param {Array} array + * @returns {Array} + */ + toArray(this: Sequence, array?: Array): Array { + const result: Array = array || []; + while (this.iterator.hasNext()) { + result.push(this.iterator.next()); + } + return result; + } + + /** + * Returns all elements of the sequence as array. If an `array` is passed + * the elements are appended to the end of the array. + * + * @param {Array} array + * @returns {Array} + */ + toList(this: Sequence, array?: Array): Array { + return this.toArray(array); } - return result; -} -export default toArray; \ No newline at end of file +} \ No newline at end of file diff --git a/src/toMap.ts b/src/toMap.ts index c3d4b79..0129291 100644 --- a/src/toMap.ts +++ b/src/toMap.ts @@ -1,21 +1,23 @@ import Sequence from "./Sequence"; -/** - * Returns a map consisting of each key-value pair. If a `map` is passed - * the pairs are set on this map. Duplicate keys override each other. - * - * @param {Map} map - * @returns {Map} - */ -function toMap(this: Sequence<[K, V]>, map?: Map): Map { - const result = map || new Map(); - while (this.iterator.hasNext()) { - const pair = this.iterator.next(); - const key = pair[0]; - const value = pair[1]; - result.set(key, value); +export class ToMap { + + /** + * Returns a map consisting of each key-value pair. If a `map` is passed + * the pairs are set on this map. Duplicate keys override each other. + * + * @param {Map} map + * @returns {Map} + */ + toMap(this: Sequence<[K, V]>, map?: Map): Map { + const result = map || new Map(); + while (this.iterator.hasNext()) { + const pair = this.iterator.next(); + const key = pair[0]; + const value = pair[1]; + result.set(key, value); + } + return result; } - return result; -} -export default toMap; \ No newline at end of file +} \ No newline at end of file diff --git a/src/toSet.ts b/src/toSet.ts index 423772a..328952b 100644 --- a/src/toSet.ts +++ b/src/toSet.ts @@ -1,19 +1,21 @@ import Sequence from "./Sequence"; -/** - * Returns all elements of the sequence as set. If a `set` is passed - * the elements are added to this set. - * - * @param {Set} set - * @returns {Set} - */ -function toSet(this: Sequence, set?: Set): Set { - const result = set || new Set(); - while (this.iterator.hasNext()) { - const item = this.iterator.next(); - result.add(item); +export class ToSet { + + /** + * Returns all elements of the sequence as set. If a `set` is passed + * the elements are added to this set. + * + * @param {Set} set + * @returns {Set} + */ + toSet(this: Sequence, set?: Set): Set { + const result = set || new Set(); + while (this.iterator.hasNext()) { + const item = this.iterator.next(); + result.add(item); + } + return result; } - return result; -} -export default toSet; \ No newline at end of file +} \ No newline at end of file diff --git a/src/unzip.ts b/src/unzip.ts index 76bef50..72a62ee 100644 --- a/src/unzip.ts +++ b/src/unzip.ts @@ -1,20 +1,22 @@ import Sequence from "./Sequence"; -/** - * Returns a pair of arrays where the first array contains all first values - * and the second array all second values from each input pair of the sequence. - * - * @returns {[Array , Array]} - */ -function unzip(this: Sequence<[T, S]>): [Array, Array] { - const array1: Array = []; - const array2: Array = []; - while (this.iterator.hasNext()) { - const [first, second] = this.iterator.next(); - array1.push(first); - array2.push(second); +export class Unzip { + + /** + * Returns a pair of arrays where the first array contains all first values + * and the second array all second values from each input pair of the sequence. + * + * @returns {[Array , Array]} + */ + unzip(this: Sequence<[T, S]>): [Array, Array] { + const array1: Array = []; + const array2: Array = []; + while (this.iterator.hasNext()) { + const [first, second] = this.iterator.next(); + array1.push(first); + array2.push(second); + } + return [array1, array2]; } - return [array1, array2]; -} -export default unzip; \ No newline at end of file +} \ No newline at end of file diff --git a/src/withIndex.ts b/src/withIndex.ts index 06d93cf..ad213fa 100644 --- a/src/withIndex.ts +++ b/src/withIndex.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import IndexedValue from "./IndexedValue"; import SequenceIterator from "./SequenceIterator"; @@ -18,13 +18,15 @@ class IndexIterator implements SequenceIterator> { } } -/** - * Returns a new sequence consisting of indexed values for all original elements. - * - * @returns {Sequence>} - */ -function withIndex(this: Sequence): Sequence> { - return new Sequence(new IndexIterator(this.iterator)); -} +export class WithIndex { + + /** + * Returns a new sequence consisting of indexed values for all original elements. + * + * @returns {Sequence>} + */ + withIndex(this: Sequence): Sequence> { + return createSequence(new IndexIterator(this.iterator)); + } -export default withIndex; \ No newline at end of file +} \ No newline at end of file diff --git a/src/zip.ts b/src/zip.ts index 7f1cb5f..1ff8d99 100644 --- a/src/zip.ts +++ b/src/zip.ts @@ -1,4 +1,4 @@ -import Sequence from "./Sequence"; +import Sequence, {createSequence} from "./Sequence"; import SequenceIterator from "./SequenceIterator"; class ZipIterator implements SequenceIterator<[T, S]> { @@ -19,16 +19,18 @@ class ZipIterator implements SequenceIterator<[T, S]> { } -/** - * Returns a new sequence consisting of pairs built the elements of both sequences - * with the same index. The resulting sequence has the length of the shortest input - * sequence. All other elements are discarded. - * - * @param {Sequence} other - * @returns {Sequence<[T , S]>} - */ -function zip(this: Sequence, other: Sequence): Sequence<[T, S]> { - return new Sequence(new ZipIterator(this.iterator, other.iterator)); -} +export class Zip { + + /** + * Returns a new sequence consisting of pairs built the elements of both sequences + * with the same index. The resulting sequence has the length of the shortest input + * sequence. All other elements are discarded. + * + * @param {Sequence} other + * @returns {Sequence<[T , S]>} + */ + zip(this: Sequence, other: Sequence): Sequence<[T, S]> { + return createSequence(new ZipIterator(this.iterator, other.iterator)); + } -export default zip; \ No newline at end of file +} \ No newline at end of file From e6c06a4b218046d6c7223a48b72e1829ee6c108d Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Thu, 12 Oct 2017 13:09:10 +0200 Subject: [PATCH 7/9] API to augment SequenceImpl prototype with user defined functions --- src/Sequence.ts | 8 +++++ test/userDefinedOperation.test.ts | 51 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 test/userDefinedOperation.test.ts diff --git a/src/Sequence.ts b/src/Sequence.ts index 00cca39..ad91f66 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -135,6 +135,14 @@ export function isSequence(object: any): object is Sequence { return object instanceof SequenceImpl; } +export function augmentSequenceWithOperator(operatorName: string, operator: any) { + SequenceImpl.prototype[operatorName] = operator; +} + +export function augmentSequenceWithMixin(mixin: any) { + applyMixins(SequenceImpl, [mixin]); +} + export function generateSequence(nextFunction: () => T | null | undefined): Sequence; export function generateSequence(seedFunction: () => T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; export function generateSequence(seed: T | null | undefined, nextFunction: (item: T) => T | null | undefined): Sequence; diff --git a/test/userDefinedOperation.test.ts b/test/userDefinedOperation.test.ts new file mode 100644 index 0000000..2e254aa --- /dev/null +++ b/test/userDefinedOperation.test.ts @@ -0,0 +1,51 @@ +import Sequence, {augmentSequenceWithMixin, augmentSequenceWithOperator, sequenceOf} from "../src/Sequence"; + +declare module "../src/Sequence" { + export default interface Sequence { + greetAll(this: Sequence): string; + } +} + +describe("User defined operation", () => { + it("should augment Sequence implementation prototype", () => { + function greetAll(this: Sequence): string { + const names = this.joinToString({ separator: ", " }); + if (names.length === 0) { + return ""; + } else { + return "Hello " + names + " !"; + } + } + + augmentSequenceWithOperator("greetAll", greetAll); + + const names = sequenceOf("John", "Bob", "Steve"); + const greetings = names.greetAll(); + expect(greetings).toBe("Hello John, Bob, Steve !"); + }); +}); + +class FarewellAll { + farewellAll(this: Sequence): string { + const names = this.joinToString({ separator: ", " }); + if (names.length === 0) { + return ""; + } else { + return "Farewell " + names + " !"; + } + } +} + +declare module "../src/Sequence" { + export default interface Sequence extends FarewellAll { + } +} + +describe("User defined operation mixin", () => { + it("should augment Sequence implementation prototype", () => { + augmentSequenceWithMixin(FarewellAll); + const names = sequenceOf("John", "Bob", "Steve"); + const greetings = names.farewellAll(); + expect(greetings).toBe("Farewell John, Bob, Steve !"); + }); +}); From f90e39fa4cd6d17b269c7f4c6a73f8236812694e Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Thu, 12 Oct 2017 13:49:23 +0200 Subject: [PATCH 8/9] Clean Sequence interface documentation by hiding hierarchy --- package.json | 2 +- src/Sequence.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 4a879b4..7ce515b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "coverage": "rimraf coverage && jest --coverage", "travis": "yarn run lint && yarn test", "lint": "node_modules/.bin/tslint -c tslint.json src/**/*.ts test/**/*.ts", - "docs": "rimraf docs && typedoc --name Sequency --readme APIDOC.md --module commonjs --out docs --target es6 --hideGenerator --gaID UA-48569937-1 src", + "docs": "rimraf docs && typedoc --name Sequency --readme APIDOC.md --module commonjs --out docs --excludeNotExported --target es6 --hideGenerator --gaID UA-48569937-1 src", "docs-publish": "yarn run docs && touch docs/.nojekyll && gh-pages -d docs -t", "build": "rimraf lib && rimraf lib-umd && yarn run lint && tsc && yarn test && webpack && size-limit", "build-prod": "yarn run build && yarn run docs-publish", diff --git a/src/Sequence.ts b/src/Sequence.ts index ad91f66..1287923 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -71,22 +71,25 @@ import {WithIndex} from "./withIndex"; import {Zip} from "./zip"; /** - * A Sequence provides a fluent functional API consisting - * of various intermediate and terminal operations for processing the iterated data. - * The operations are evaluated lazily to avoid examining all of the input data - * when it's not necessary. Sequences can be iterated only once. + * @hidden */ -export default interface Sequence extends All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop, +export interface SequenceOperators extends All, Any, AsIterable, Associate, AssociateBy, Average, Chunk, Contains, Count, Distinct, DistinctBy, Drop, DropWhile, ElementAt, ElementAtOrElse, ElementAtOrNull, Filter, FilterIndexed, FilterNot, FilterNotNull, First, FirstOrNull, FlatMap, Flatten, Fold, FoldIndexed, ForEach, ForEachIndexed, GroupBy, IndexOf, IndexOfFirst, IndexOfLast, JoinToString, Last, LastOrNull, Map, MapIndexed, MapNotNull, Max, MaxBy, MaxWith, Merge, Min, MinBy, Minus, MinWith, None, OnEach, Partition, Plus, Reduce, ReduceIndexed, Reverse, Single, SingleOrNull, Sorted, SortedBy, SortedByDescending, SortedDescending, SortedWith, Sum, SumBy, Take, TakeWhile, ToArray, ToMap, ToSet, Unzip, WithIndex, Zip { - readonly iterator: SequenceIterator; } /** - * @hidden + * A Sequence provides a fluent functional API consisting + * of various intermediate and terminal operations for processing the iterated data. + * The operations are evaluated lazily to avoid examining all of the input data + * when it's not necessary. Sequences can be iterated only once. */ +export default interface Sequence extends SequenceOperators { + readonly iterator: SequenceIterator; +} + class SequenceImpl { constructor(readonly iterator: SequenceIterator) { } From a4ecf26c2fef16cd5b3bb5106cdb5dc74bbda018 Mon Sep 17 00:00:00 2001 From: Mikael Couzic Date: Mon, 16 Oct 2017 10:41:01 +0200 Subject: [PATCH 9/9] extendSequence() with mixins --- src/Sequence.ts | 6 +--- test/extendSequence.test.ts | 22 +++++++++++++ test/userDefinedOperation.test.ts | 51 ------------------------------- 3 files changed, 23 insertions(+), 56 deletions(-) create mode 100644 test/extendSequence.test.ts delete mode 100644 test/userDefinedOperation.test.ts diff --git a/src/Sequence.ts b/src/Sequence.ts index 1287923..283467f 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -138,11 +138,7 @@ export function isSequence(object: any): object is Sequence { return object instanceof SequenceImpl; } -export function augmentSequenceWithOperator(operatorName: string, operator: any) { - SequenceImpl.prototype[operatorName] = operator; -} - -export function augmentSequenceWithMixin(mixin: any) { +export function extendSequence(mixin: { new(): any }) { applyMixins(SequenceImpl, [mixin]); } diff --git a/test/extendSequence.test.ts b/test/extendSequence.test.ts new file mode 100644 index 0000000..0c8cdd4 --- /dev/null +++ b/test/extendSequence.test.ts @@ -0,0 +1,22 @@ +import Sequence, {extendSequence, sequenceOf} from "../src/Sequence"; + +class GreetAll { + greetAll(this: Sequence): string { + const names = this.joinToString({ separator: ", " }); + return "Hello " + names + " !"; + } +} + +declare module "../src/Sequence" { + export default interface Sequence extends GreetAll { + } +} + +describe("extendSequence", () => { + it("should extend Sequence implementation prototype with provided mixin", () => { + extendSequence(GreetAll); + const names = sequenceOf("John", "Bob", "Steve"); + const greetings = names.greetAll(); + expect(greetings).toBe("Hello John, Bob, Steve !"); + }); +}); diff --git a/test/userDefinedOperation.test.ts b/test/userDefinedOperation.test.ts deleted file mode 100644 index 2e254aa..0000000 --- a/test/userDefinedOperation.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import Sequence, {augmentSequenceWithMixin, augmentSequenceWithOperator, sequenceOf} from "../src/Sequence"; - -declare module "../src/Sequence" { - export default interface Sequence { - greetAll(this: Sequence): string; - } -} - -describe("User defined operation", () => { - it("should augment Sequence implementation prototype", () => { - function greetAll(this: Sequence): string { - const names = this.joinToString({ separator: ", " }); - if (names.length === 0) { - return ""; - } else { - return "Hello " + names + " !"; - } - } - - augmentSequenceWithOperator("greetAll", greetAll); - - const names = sequenceOf("John", "Bob", "Steve"); - const greetings = names.greetAll(); - expect(greetings).toBe("Hello John, Bob, Steve !"); - }); -}); - -class FarewellAll { - farewellAll(this: Sequence): string { - const names = this.joinToString({ separator: ", " }); - if (names.length === 0) { - return ""; - } else { - return "Farewell " + names + " !"; - } - } -} - -declare module "../src/Sequence" { - export default interface Sequence extends FarewellAll { - } -} - -describe("User defined operation mixin", () => { - it("should augment Sequence implementation prototype", () => { - augmentSequenceWithMixin(FarewellAll); - const names = sequenceOf("John", "Bob", "Steve"); - const greetings = names.farewellAll(); - expect(greetings).toBe("Farewell John, Bob, Steve !"); - }); -});