diff --git a/README.md b/README.md index 069c892..85b8412 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ import { - `generateSequence` returns a sequence generated from the given generator function. - `extendSequence` allows extending sequences with user-defined operations (see [example](https://github.com/Kraken-crypto/sequency/blob/master/test/extendSequence.test.ts)). -Each `Sequence` provides a fluent functional API consisting of intermediate and terminal operations. Intermediate functions (e.g. `filter`, `map`, `sorted`) return a new sequence, thus enabling method chaining. Terminal functions (e.g. `toArray`, `groupBy`, `findLast`) return an arbitrary result. Detailed descriptions of all operations are available in the [API docs](https://github.com/Kraken-crypto/sequency). +Each `Sequence` provides a fluent functional API consisting of intermediate and terminal operations. Intermediate functions (e.g. `filter`, `map`, `sorted`) return a new sequence, thus enabling method chaining. Terminal functions (e.g. `toArray`, `groupBy`, `findLast`) return an arbitrary result. Detailed descriptions of all operations are available in the [API docs](https://kraken-crypto.github.io/sequency). Sequences are **lazily evaluated** to avoid examining all of the input data when it's not necessary. Sequences always perform the minimal amount of operations to gain results. E.g. in a `filter - map - find` sequence both `map` and `find` are executed just one time before returning the single result. @@ -113,7 +113,9 @@ Sequences are **lazily evaluated** to avoid examining all of the input data when ## API documentation -Sequency is fully documented via inline JSDoc comments. When using an IDE like Intellij IDEA or Webstorm the docs are available inline right inside your editor. +Full API documentation is available at [Sequency API docs](https://kraken-crypto.github.io/sequency). + +Sequency is also fully documented via inline JSDoc comments. When using an IDE like Intellij IDEA or Webstorm the docs are available inline right inside your editor. ## Why Sequency? diff --git a/src/AsyncSequence.ts b/src/AsyncSequence.ts index 94b5c18..a274cf2 100644 --- a/src/AsyncSequence.ts +++ b/src/AsyncSequence.ts @@ -31,6 +31,8 @@ import {GroupBy as GroupByOp} from "./operators/async/groupBy"; import {IndexOf as IndexOfOp} from "./operators/async/indexOf"; import {IndexOfFirst as IndexOfFirstOp} from "./operators/async/indexOfFirst"; import {IndexOfLast as IndexOfLastOp} from "./operators/async/indexOfLast"; +import {IsEmpty as IsEmptyOp} from "./operators/async/isEmpty"; +import {IsNotEmpty as IsNotEmptyOp} from "./operators/async/isNotEmpty"; import {JoinToString as JoinToStringOp} from "./operators/async/joinToString"; import {Last as LastOp} from "./operators/async/last"; import {LastOrNull as LastOrNullOp} from "./operators/async/lastOrNull"; @@ -87,7 +89,7 @@ export interface AsyncSequence extends AsyncSequenceOperators { */ export interface AsyncSequenceOperators extends AllOp, AnyOp, AsIterableOp, AssociateOp, AssociateByOp, AverageOp, ChunkOp, ContainsOp, CountOp, DistinctOp, DistinctByOp, DropOp, DropWhileOp, ElementAtOp, ElementAtOrElseOp, ElementAtOrNullOp, FilterOp, FilterIndexedOp, FilterNotOp, FilterNotNullOp, FirstOp, FirstOrNullOp, FlatMapOp, FlattenOp, FoldOp, - FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, + FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, IsEmptyOp, IsNotEmptyOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, MaxWithOp, MergeOp, MinOp, MinByOp, MinusOp, MinWithOp, NoneOp, OnEachOp, OnEachIndexedOp, PartitionOp, PlusOp, ReduceOp, ReduceIndexedOp, ReverseOp, SingleOp, SingleOrNullOp, SortedOp, SortedByOp, SortedByDescendingOp, SortedDescendingOp, SortedWithOp, SumOp, SumByOp, TakeOp, TakeWhileOp, ToArrayOp, ToMapOp, ToSetOp, ToSequenceOp, UnzipOp, WithIndexOp, ZipOp { } @@ -99,6 +101,6 @@ export class AsyncSequenceImpl { applyMixins(AsyncSequenceImpl, [AllOp, AnyOp, AsIterableOp, AssociateOp, AssociateByOp, AverageOp, ChunkOp, ContainsOp, CountOp, DistinctOp, DistinctByOp, DropOp, DropWhileOp, ElementAtOp, ElementAtOrElseOp, ElementAtOrNullOp, FilterOp, FilterIndexedOp, FilterNotOp, FilterNotNullOp, FirstOp, FirstOrNullOp, FlatMapOp, FlattenOp, FoldOp, - FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, + FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, IsEmptyOp, IsNotEmptyOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, MaxWithOp, MergeOp, MinOp, MinByOp, MinusOp, MinWithOp, NoneOp, OnEachOp, OnEachIndexedOp, PartitionOp, PlusOp, ReduceOp, ReduceIndexedOp, ReverseOp, SingleOp, SingleOrNullOp, SortedOp, SortedByOp, SortedByDescendingOp, SortedDescendingOp, SortedWithOp, SumOp, SumByOp, TakeOp, TakeWhileOp, ToArrayOp, ToMapOp, ToSetOp, ToSequenceOp, UnzipOp, WithIndexOp, ZipOp]); \ No newline at end of file diff --git a/src/Sequence.ts b/src/Sequence.ts index faad1ef..2f33cf8 100644 --- a/src/Sequence.ts +++ b/src/Sequence.ts @@ -32,6 +32,8 @@ import {GroupBy as GroupByOp} from "./operators/sync/groupBy"; import {IndexOf as IndexOfOp} from "./operators/sync/indexOf"; import {IndexOfFirst as IndexOfFirstOp} from "./operators/sync/indexOfFirst"; import {IndexOfLast as IndexOfLastOp} from "./operators/sync/indexOfLast"; +import {IsEmpty as IsEmptyOp} from "./operators/sync/isEmpty"; +import {IsNotEmpty as IsNotEmptyOp} from "./operators/sync/isNotEmpty"; import {JoinToString as JoinToStringOp} from "./operators/sync/joinToString"; import {Last as LastOp} from "./operators/sync/last"; import {LastOrNull as LastOrNullOp} from "./operators/sync/lastOrNull"; @@ -87,7 +89,7 @@ export interface Sequence extends SequenceOperators { */ export interface SequenceOperators extends AllOp, AnyOp, AsIterableOp, AssociateOp, AssociateByOp, AverageOp, ChunkOp, ContainsOp, CountOp, DistinctOp, DistinctByOp, DropOp, DropWhileOp, ElementAtOp, ElementAtOrElseOp, ElementAtOrNullOp, FilterOp, FilterIndexedOp, FilterNotOp, FilterNotNullOp, FirstOp, FirstOrNullOp, FlatMapOp, FlattenOp, FoldOp, - FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, + FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, IsEmptyOp, IsNotEmptyOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, MaxWithOp, MergeOp, MinOp, MinByOp, MinusOp, MinWithOp, NoneOp, OnEachOp, OnEachIndexedOp, PartitionOp, PlusOp, ReduceOp, ReduceIndexedOp, ReverseOp, SingleOp, SingleOrNullOp, SortedOp, SortedByOp, SortedByDescendingOp, SortedDescendingOp, SortedWithOp, SumOp, SumByOp, TakeOp, TakeWhileOp, ToArrayOp, ToAsyncSequenceOp, ToMapOp, ToSetOp, UnzipOp, WithIndexOp, ZipOp { } @@ -99,6 +101,6 @@ export class SequenceImpl { applyMixins(SequenceImpl, [AllOp, AnyOp, AsIterableOp, AssociateOp, AssociateByOp, AverageOp, ChunkOp, ContainsOp, CountOp, DistinctOp, DistinctByOp, DropOp, DropWhileOp, ElementAtOp, ElementAtOrElseOp, ElementAtOrNullOp, FilterOp, FilterIndexedOp, FilterNotOp, FilterNotNullOp, FirstOp, FirstOrNullOp, FlatMapOp, FlattenOp, FoldOp, - FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, + FoldIndexedOp, ForEachOp, ForEachIndexedOp, GroupByOp, IndexOfOp, IndexOfFirstOp, IndexOfLastOp, IsEmptyOp, IsNotEmptyOp, JoinToStringOp, LastOp, LastOrNullOp, MapOp, MapIndexedOp, MapNotNullOp, MaxOp, MaxByOp, MaxWithOp, MergeOp, MinOp, MinByOp, MinusOp, MinWithOp, NoneOp, OnEachOp, OnEachIndexedOp, PartitionOp, PlusOp, ReduceOp, ReduceIndexedOp, ReverseOp, SingleOp, SingleOrNullOp, SortedOp, - SortedByOp, SortedByDescendingOp, SortedDescendingOp, SortedWithOp, SumOp, SumByOp, TakeOp, TakeWhileOp, ToArrayOp, ToAsyncSequenceOp, ToMapOp, ToSetOp, UnzipOp, WithIndexOp, ZipOp]); \ No newline at end of file + SortedByOp, SortedByDescendingOp, SortedDescendingOp, SortedWithOp, SumOp, SumByOp, TakeOp, TakeWhileOp, ToArrayOp, ToAsyncSequenceOp, ToMapOp, ToSetOp, UnzipOp, WithIndexOp, ZipOp]); diff --git a/src/operators/async/isEmpty.ts b/src/operators/async/isEmpty.ts new file mode 100644 index 0000000..0f77ca8 --- /dev/null +++ b/src/operators/async/isEmpty.ts @@ -0,0 +1,14 @@ +import {AsyncSequence} from "../../sequency"; + +export class IsEmpty { + + /** + * Returns `true` the sequence is empty + * + * @returns {Promise} + */ + async isEmpty(this: AsyncSequence): Promise { + return !(await this.isNotEmpty()); + } + +} diff --git a/src/operators/async/isNotEmpty.ts b/src/operators/async/isNotEmpty.ts new file mode 100644 index 0000000..29a2c92 --- /dev/null +++ b/src/operators/async/isNotEmpty.ts @@ -0,0 +1,14 @@ +import {AsyncSequence} from "../../sequency"; + +export class IsNotEmpty { + + /** + * Returns `true` the sequence is not empty + * + * @returns {Promise} + */ + async isNotEmpty(this: AsyncSequence): Promise { + return await this.any(() => true); + } + +} diff --git a/src/operators/sync/isEmpty.ts b/src/operators/sync/isEmpty.ts new file mode 100644 index 0000000..0c09677 --- /dev/null +++ b/src/operators/sync/isEmpty.ts @@ -0,0 +1,14 @@ +import {Sequence} from "../../sequency"; + +export class IsEmpty { + + /** + * Returns `true` the sequence is empty + * + * @returns {boolean} + */ + isEmpty(this: Sequence): boolean { + return !this.isNotEmpty(); + } + +} diff --git a/src/operators/sync/isNotEmpty.ts b/src/operators/sync/isNotEmpty.ts new file mode 100644 index 0000000..e7a1c2a --- /dev/null +++ b/src/operators/sync/isNotEmpty.ts @@ -0,0 +1,14 @@ +import {Sequence} from "../../sequency"; + +export class IsNotEmpty { + + /** + * Returns `true` the sequence is not empty + * + * @returns {boolean} + */ + isNotEmpty(this: Sequence): boolean { + return this.any(() => true); + } + +} diff --git a/test/operators/async/isEmpty.test.ts b/test/operators/async/isEmpty.test.ts new file mode 100644 index 0000000..e1abcb1 --- /dev/null +++ b/test/operators/async/isEmpty.test.ts @@ -0,0 +1,39 @@ +import {asyncSequenceOf, emptyAsyncSequence, asAsyncSequence} from "../../../src/sequency"; + +describe("isEmpty", () => { + it("returns false for infinite sequences", async () => { + const infiniteSequence = asAsyncSequence((async function* () { + let i = 0; + while (true) { + yield i++; + } + })()); + const result = await infiniteSequence.isEmpty(); + expect(result).toBeFalsy(); + }); + + it("returns false for non-empty sequences", async () => { + const result = await asyncSequenceOf(1, 2, 3).isEmpty(); + expect(result).toBeFalsy(); + }); + + it("returns true for empty sequences", async () => { + const result = await emptyAsyncSequence().isEmpty(); + expect(result).toBeTruthy(); + }); + + it("returns true for sequences that would be empty", async () => { + const result = await asyncSequenceOf(1, 3) + .filter(async x => x % 2 === 0) + .isEmpty(); + expect(result).toBeTruthy(); + }); + + it("returns false for sequences that would not be empty", async () => { + const result = await asyncSequenceOf(1, 3) + .filter(async x => x % 2 !== 0) + .isEmpty(); + expect(result).toBeFalsy(); + }); +}); + diff --git a/test/operators/async/isNotEmpty.test.ts b/test/operators/async/isNotEmpty.test.ts new file mode 100644 index 0000000..82dba75 --- /dev/null +++ b/test/operators/async/isNotEmpty.test.ts @@ -0,0 +1,39 @@ +import {asyncSequenceOf, emptyAsyncSequence, asAsyncSequence} from "../../../src/sequency"; + +describe("isNotEmpty", () => { + it("returns true for infinite sequences", async () => { + const infiniteSequence = asAsyncSequence((async function* () { + let i = 0; + while (true) { + yield i++; + } + })()); + const result = await infiniteSequence.isNotEmpty(); + expect(result).toBeTruthy(); + }); + + it("returns true for non-empty sequences", async () => { + const result = await asyncSequenceOf(1, 2, 3).isNotEmpty(); + expect(result).toBeTruthy(); + }); + + it("returns false for empty sequences", async () => { + const result = await emptyAsyncSequence().isNotEmpty(); + expect(result).toBeFalsy(); + }); + + it("returns false for sequences that would be empty", async () => { + const result = await asyncSequenceOf(1, 3) + .filter(async x => x % 2 === 0) + .isNotEmpty(); + expect(result).toBeFalsy(); + }); + + it("returns true for sequences that would not be empty", async () => { + const result = await asyncSequenceOf(1, 3) + .filter(async x => x % 2 !== 0) + .isNotEmpty(); + expect(result).toBeTruthy(); + }); +}); + diff --git a/test/operators/sync/isEmpty.test.ts b/test/operators/sync/isEmpty.test.ts new file mode 100644 index 0000000..8d9a77d --- /dev/null +++ b/test/operators/sync/isEmpty.test.ts @@ -0,0 +1,20 @@ +import {range, sequenceOf} from "../../../src/sequency"; + +describe("isEmpty", () => { + it("returns false for infinite sequences", () => { + expect(range(0, Infinity).isEmpty()).toBeFalsy(); + }); + + it("returns true for empty sequences", () => { + expect(sequenceOf().isEmpty()).toBeTruthy(); + }); + + it("returns true for sequences that would be empty", () => { + expect(sequenceOf(1, 3).filter(x => x % 2 === 0).isEmpty()).toBeTruthy(); + }); + + it("returns false for sequences that would not be empty", () => { + expect(sequenceOf(1, 3).filter(x => x % 2 !== 0).isEmpty()).toBeFalsy(); + }); +}); + diff --git a/test/operators/sync/isNotEmpty.test.ts b/test/operators/sync/isNotEmpty.test.ts new file mode 100644 index 0000000..c88f4f8 --- /dev/null +++ b/test/operators/sync/isNotEmpty.test.ts @@ -0,0 +1,20 @@ +import {range, sequenceOf} from "../../../src/sequency"; + +describe("isNotEmpty", () => { + it("returns true for infinite sequences", () => { + expect(range(0, Infinity).isNotEmpty()).toBeTruthy(); + }); + + it("returns false for empty sequences", () => { + expect(sequenceOf().isNotEmpty()).toBeFalsy(); + }); + + it("returns false for sequences that would be empty", () => { + expect(sequenceOf(1, 3).filter(x => x % 2 === 0).isNotEmpty()).toBeFalsy(); + }); + + it("returns true for sequences that would not be empty", () => { + expect(sequenceOf(1, 3).filter(x => x % 2 !== 0).isNotEmpty()).toBeTruthy(); + }); +}); +