iterable-linq-utility 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -59,6 +59,26 @@ pnpm bench chains # only the chains
59
59
  BENCH_TIME=1000 pnpm bench # more time for each case (default 500 ms): more samples, less noise
60
60
  ```
61
61
 
62
+ ### Report
63
+
64
+ A pull request that adds an operation or changes its performance pastes the report in its description, as it is:
65
+
66
+ ```sh
67
+ pnpm bench:report functions/sum # the same filter as pnpm bench
68
+ BENCH_HOST="MacBook Pro M3" pnpm bench:report functions/sum # names the host, which a container cannot see
69
+ ```
70
+
71
+ ```text
72
+ Environment: Alpine Linux v3.24 (Docker), arm64, 12 cores, CPU unknown · host MacBook Pro M3 · Node v24.20.0 · Vitest 5.0.2 · BENCH_TIME 500 ms · commit 2fba44d
73
+
74
+ | case | variant | ops/s | mean (ms) | p75 (ms) | p99 (ms) | rme | vs native |
75
+ | --- | --- | --- | --- | --- | --- | --- | --- |
76
+ | sum/map | native | 567 | 1.79 | 1.76 | 3.64 | ±2.5% | — |
77
+ | sum/map | chain | 855 | 1.2 | 1.26 | 1.76 | ±1.8% | +51% |
78
+ ```
79
+
80
+ `vs native` compares the ops/s with the `native` case of the same group. When a baseline exists, a `vs baseline` column compares them with it. [ADR 0021](docs/decisions/0021-benchmark-standards.md) explains the format.
81
+
62
82
  ### Find regressions
63
83
 
64
84
  A baseline is a saved run. Each table shows it as extra rows, marked `(baseline)`, next to the new run.
@@ -68,6 +88,7 @@ git switch main
68
88
  pnpm bench:baseline # saves every result in .bench/
69
89
  git switch my-branch
70
90
  pnpm bench # shows each case next to its "(baseline)" row
91
+ pnpm bench:report functions/sum # or the report, with a "vs baseline" column
71
92
  ```
72
93
 
73
94
  The timings depend on the machine, so `.bench/` is not committed and the benchmarks do not run in CI. Create the baseline and the new run on the same machine, with the same load.
@@ -76,7 +97,7 @@ The timings depend on the machine, so `.bench/` is not committed and the benchma
76
97
 
77
98
  | Column | Meaning |
78
99
  |---|---|
79
- | `hz` | runs per second: higher is faster |
100
+ | `hz` | runs per second (ops/s in the report): higher is faster |
80
101
  | `mean`, `p75`, `p99` | time of one run, in ms |
81
102
  | `rme` | relative margin of error |
82
103
  | `samples` | how many runs were measured |
@@ -85,26 +106,28 @@ A difference smaller than the `rme` of the two rows is noise.
85
106
 
86
107
  ### Add a benchmark
87
108
 
88
- Create `test/bench/functions/<name>.bench.ts` or `test/bench/chains/<scenario>.bench.ts`:
109
+ Create `test/bench/functions/<name>.bench.ts`. `scenarios()` registers the standard groups of [ADR 0021](docs/decisions/0021-benchmark-standards.md) and [ADR 0022](docs/decisions/0022-benchmark-scenarios-in-practice.md): `<name>/direct` on `numbers`, `<name>/small` on `small`, `<name>/map` and `<name>/filter` after `map(double)` and `filter(isEven)`. `group()` adds one more group on `numbers`: an early exit (`start`, `middle`) or an optional callback.
89
110
 
90
111
  ```ts
91
- import { test } from 'vitest';
92
112
  import * as Helpers from '../helpers';
93
113
 
94
114
  import * as IterableLinq from 'iterable-linq-utility';
95
115
 
96
116
  // Read the exports once: an imported binding goes through a module runner getter on every read.
97
- const { from } = IterableLinq;
98
- const { cases, numbers, sum } = Helpers;
99
-
100
- test('map: number', async ({ bench }) => {
101
- await cases(bench, 'map/number') // the baseline folder: <function>/<variant>
102
- .add('native', () => sum(numbers.map(v => v * 2)))
103
- .add('chain', () => sum(from(numbers).map(v => v * 2)))
104
- .run();
117
+ const { Functions } = IterableLinq;
118
+ const { double, scenarios, sum } = Helpers;
119
+
120
+ scenarios('map', {
121
+ native: values => sum(values.map(double)), // an array
122
+ chain: chain => sum(chain.map(double)), // a chain
123
+ Functions: values => sum(Functions.map(values, double)) // an iterable
105
124
  });
106
125
  ```
107
126
 
127
+ - `native` is the array method a user would write. Add `loop`, a hand-written `for…of`, when the array method is a different algorithm (`reduce` for `sum`); when there is no array method, `native` is the loop.
128
+ - `pnpm check:structure` fails when the bench of an operation has no `direct` or `small` group. `test/bench/functions/sum.bench.ts` is the example.
129
+ - The benches of `test/bench/chains/<scenario>.bench.ts`, and the groups that need other data, use `cases(bench, '<function>/<group>')` directly: the id is also the baseline folder.
130
+
108
131
  - Every table needs at least two cases: add a native reference.
109
132
  - Import the library and the helpers as namespaces and copy the exports into local constants, as above. Vitest prints a `Benchmark Warning` when a benchmark reads an imported binding too many times.
110
- - The shared data is in `test/bench/helpers.ts`: `numbers` (100,000 integers), `records` (100,000 objects) and `small` (1,000 integers).
133
+ - The shared data is in `test/bench/helpers.ts`: `numbers` (100,000 integers) for every scenario, `small` (1,000 integers) for `<name>/small`, `records` (100,000 objects) only for a key or a selector on objects. The callbacks and values of the scenarios are there too: `double`, `isEven`, `first`, `middle`, `missing`.
package/dist/index.d.cts CHANGED
@@ -515,14 +515,17 @@ declare namespace Functions {
515
515
  range,
516
516
  reduce,
517
517
  repeat_2 as repeat,
518
+ reverse,
518
519
  sequenceEqual,
519
520
  single,
520
521
  skip,
522
+ skipLast,
521
523
  skipWhile,
522
524
  slice,
523
525
  some,
524
526
  sum,
525
527
  take,
528
+ takeLast,
526
529
  takeWhile,
527
530
  tap,
528
531
  tapChain
@@ -1030,6 +1033,19 @@ export declare interface IIterableLinqBase<T> {
1030
1033
  * @since 0.0.10
1031
1034
  */
1032
1035
  reduce<R>(neutralElement: R, reducer: Reducer<T, R>): R;
1036
+ /**
1037
+ * Lazily yields the values in reverse order. Unlike `Array.prototype.reverse`, the source is not changed.
1038
+ * The whole chain runs before the first value is yielded, so `reverse` does not end on an infinite chain.
1039
+ * Every run of the chain reads the source again.
1040
+ * @operation `Transformation`
1041
+ * @returns a lazy, re-runnable chain of the values from the last to the first
1042
+ * @example
1043
+ * ```ts
1044
+ * IterableLinq.from([1, 2, 3]).reverse().collectToArray(); // [3, 2, 1]
1045
+ * ```
1046
+ * @since 0.9.0
1047
+ */
1048
+ reverse(): IIterableLinq<T>;
1033
1049
  /**
1034
1050
  * Tells whether the chain and `other` have the same values in the same order.
1035
1051
  * It reads the two sources side by side, and stops and closes both at the first difference or when one ends before the other.
@@ -1103,6 +1119,20 @@ export declare interface IIterableLinqBase<T> {
1103
1119
  * @since 0.3.0
1104
1120
  */
1105
1121
  skip(count: number): IIterableLinq<T>;
1122
+ /**
1123
+ * Lazily yields every value except the last `count`.
1124
+ * A value is yielded once `count` more values have been read, keeping only those `count` values, so `skipLast` works with infinite chains.
1125
+ * @operation `Transformation`
1126
+ * @param count - how many values to leave out at the end; must be a non-negative integer
1127
+ * @returns a lazy, re-runnable chain of the values before the last `count`
1128
+ * @throws Error if `count` is negative, not an integer, `NaN` or `Infinity`
1129
+ * @example
1130
+ * ```ts
1131
+ * IterableLinq.from([1, 2, 3, 4, 5]).skipLast(2).collectToArray(); // [1, 2, 3]
1132
+ * ```
1133
+ * @since 0.9.0
1134
+ */
1135
+ skipLast(count: number): IIterableLinq<T>;
1106
1136
  /**
1107
1137
  * Skips the values while `predicate` returns `true`, then yields the first rejected value and all the rest.
1108
1138
  * After the first rejected value, `predicate` is not called again.
@@ -1200,6 +1230,20 @@ export declare interface IIterableLinqBase<T> {
1200
1230
  * @since 0.3.0
1201
1231
  */
1202
1232
  take(count: number): IIterableLinq<T>;
1233
+ /**
1234
+ * Lazily yields the last `count` values.
1235
+ * The whole chain runs before the first value is yielded, keeping only the last `count` values, so `takeLast` does not end on an infinite chain.
1236
+ * @operation `Transformation`
1237
+ * @param count - how many values to yield; must be a non-negative integer
1238
+ * @returns a lazy, re-runnable chain of at most `count` values
1239
+ * @throws Error if `count` is negative, not an integer, `NaN` or `Infinity`
1240
+ * @example
1241
+ * ```ts
1242
+ * IterableLinq.from([1, 2, 3, 4, 5]).takeLast(2).collectToArray(); // [4, 5]
1243
+ * ```
1244
+ * @since 0.9.0
1245
+ */
1246
+ takeLast(count: number): IIterableLinq<T>;
1203
1247
  /**
1204
1248
  * Yields the values while a type guard accepts them, narrowing their type, then closes the source.
1205
1249
  * The source is never read past the first rejected value, which is not yielded.
@@ -1625,6 +1669,22 @@ export declare function repeat<T>(value: T, count: number): IIterableLinq<T>;
1625
1669
  */
1626
1670
  declare function repeat_2<T>(value: T, count: number): Iterable<T>;
1627
1671
 
1672
+ /**
1673
+ * Lazily yields the values in reverse order. Unlike `Array.prototype.reverse`, the source is not changed.
1674
+ * The whole source is read before the first value is yielded, so `reverse` does not end on an infinite source.
1675
+ * Every iteration reads the source again.
1676
+ * @operation `Transformation`
1677
+ * @param iterable - the source `Iterable`
1678
+ * @returns a lazy, re-runnable `Iterable` of the values from the last to the first
1679
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`
1680
+ * @example
1681
+ * ```ts
1682
+ * Array.from(Functions.reverse([1, 2, 3])); // [3, 2, 1]
1683
+ * ```
1684
+ * @since 0.9.0
1685
+ */
1686
+ declare function reverse<T>(iterable: Iterable<T>): Iterable<T>;
1687
+
1628
1688
  /**
1629
1689
  * Tells whether `iterable` and `other` have the same values in the same order.
1630
1690
  * It reads the two sources side by side, and stops and closes both at the first difference or when one ends before the other.
@@ -1711,6 +1771,22 @@ declare function single<T>(iterable: Iterable<T>, predicate: Predicate<T> | unde
1711
1771
  */
1712
1772
  declare function skip<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1713
1773
 
1774
+ /**
1775
+ * Lazily yields every value except the last `count`.
1776
+ * A value is yielded once `count` more values have been read, keeping only those `count` values, so `skipLast` works with infinite sources.
1777
+ * @operation `Transformation`
1778
+ * @param iterable - the source `Iterable`
1779
+ * @param count - how many values to leave out at the end; must be a non-negative integer
1780
+ * @returns a lazy, re-runnable `Iterable` of the values before the last `count`
1781
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`, or if `count` is negative, not an integer, `NaN` or `Infinity`
1782
+ * @example
1783
+ * ```ts
1784
+ * Array.from(Functions.skipLast([1, 2, 3, 4, 5], 2)); // [1, 2, 3]
1785
+ * ```
1786
+ * @since 0.9.0
1787
+ */
1788
+ declare function skipLast<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1789
+
1714
1790
  /**
1715
1791
  * Lazily skips the values while `predicate` returns `true`, then yields the first rejected value and all the rest.
1716
1792
  * After the first rejected value, `predicate` is not called again.
@@ -1824,6 +1900,22 @@ declare function sum<T>(iterable: Iterable<T>, selector: Mapper<T, number> | und
1824
1900
  */
1825
1901
  declare function take<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1826
1902
 
1903
+ /**
1904
+ * Lazily yields the last `count` values.
1905
+ * The whole source is read before the first value is yielded, keeping only the last `count` values, so `takeLast` does not end on an infinite source.
1906
+ * @operation `Transformation`
1907
+ * @param iterable - the source `Iterable`
1908
+ * @param count - how many values to yield; must be a non-negative integer
1909
+ * @returns a lazy, re-runnable `Iterable` of at most `count` values
1910
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`, or if `count` is negative, not an integer, `NaN` or `Infinity`
1911
+ * @example
1912
+ * ```ts
1913
+ * Array.from(Functions.takeLast([1, 2, 3, 4, 5], 2)); // [4, 5]
1914
+ * ```
1915
+ * @since 0.9.0
1916
+ */
1917
+ declare function takeLast<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1918
+
1827
1919
  /**
1828
1920
  * Lazily yields the values while a type guard accepts them, narrowing their type, then closes the source.
1829
1921
  * The source is never read past the first rejected value, which is not yielded.
package/dist/index.d.ts CHANGED
@@ -515,14 +515,17 @@ declare namespace Functions {
515
515
  range,
516
516
  reduce,
517
517
  repeat_2 as repeat,
518
+ reverse,
518
519
  sequenceEqual,
519
520
  single,
520
521
  skip,
522
+ skipLast,
521
523
  skipWhile,
522
524
  slice,
523
525
  some,
524
526
  sum,
525
527
  take,
528
+ takeLast,
526
529
  takeWhile,
527
530
  tap,
528
531
  tapChain
@@ -1030,6 +1033,19 @@ export declare interface IIterableLinqBase<T> {
1030
1033
  * @since 0.0.10
1031
1034
  */
1032
1035
  reduce<R>(neutralElement: R, reducer: Reducer<T, R>): R;
1036
+ /**
1037
+ * Lazily yields the values in reverse order. Unlike `Array.prototype.reverse`, the source is not changed.
1038
+ * The whole chain runs before the first value is yielded, so `reverse` does not end on an infinite chain.
1039
+ * Every run of the chain reads the source again.
1040
+ * @operation `Transformation`
1041
+ * @returns a lazy, re-runnable chain of the values from the last to the first
1042
+ * @example
1043
+ * ```ts
1044
+ * IterableLinq.from([1, 2, 3]).reverse().collectToArray(); // [3, 2, 1]
1045
+ * ```
1046
+ * @since 0.9.0
1047
+ */
1048
+ reverse(): IIterableLinq<T>;
1033
1049
  /**
1034
1050
  * Tells whether the chain and `other` have the same values in the same order.
1035
1051
  * It reads the two sources side by side, and stops and closes both at the first difference or when one ends before the other.
@@ -1103,6 +1119,20 @@ export declare interface IIterableLinqBase<T> {
1103
1119
  * @since 0.3.0
1104
1120
  */
1105
1121
  skip(count: number): IIterableLinq<T>;
1122
+ /**
1123
+ * Lazily yields every value except the last `count`.
1124
+ * A value is yielded once `count` more values have been read, keeping only those `count` values, so `skipLast` works with infinite chains.
1125
+ * @operation `Transformation`
1126
+ * @param count - how many values to leave out at the end; must be a non-negative integer
1127
+ * @returns a lazy, re-runnable chain of the values before the last `count`
1128
+ * @throws Error if `count` is negative, not an integer, `NaN` or `Infinity`
1129
+ * @example
1130
+ * ```ts
1131
+ * IterableLinq.from([1, 2, 3, 4, 5]).skipLast(2).collectToArray(); // [1, 2, 3]
1132
+ * ```
1133
+ * @since 0.9.0
1134
+ */
1135
+ skipLast(count: number): IIterableLinq<T>;
1106
1136
  /**
1107
1137
  * Skips the values while `predicate` returns `true`, then yields the first rejected value and all the rest.
1108
1138
  * After the first rejected value, `predicate` is not called again.
@@ -1200,6 +1230,20 @@ export declare interface IIterableLinqBase<T> {
1200
1230
  * @since 0.3.0
1201
1231
  */
1202
1232
  take(count: number): IIterableLinq<T>;
1233
+ /**
1234
+ * Lazily yields the last `count` values.
1235
+ * The whole chain runs before the first value is yielded, keeping only the last `count` values, so `takeLast` does not end on an infinite chain.
1236
+ * @operation `Transformation`
1237
+ * @param count - how many values to yield; must be a non-negative integer
1238
+ * @returns a lazy, re-runnable chain of at most `count` values
1239
+ * @throws Error if `count` is negative, not an integer, `NaN` or `Infinity`
1240
+ * @example
1241
+ * ```ts
1242
+ * IterableLinq.from([1, 2, 3, 4, 5]).takeLast(2).collectToArray(); // [4, 5]
1243
+ * ```
1244
+ * @since 0.9.0
1245
+ */
1246
+ takeLast(count: number): IIterableLinq<T>;
1203
1247
  /**
1204
1248
  * Yields the values while a type guard accepts them, narrowing their type, then closes the source.
1205
1249
  * The source is never read past the first rejected value, which is not yielded.
@@ -1625,6 +1669,22 @@ export declare function repeat<T>(value: T, count: number): IIterableLinq<T>;
1625
1669
  */
1626
1670
  declare function repeat_2<T>(value: T, count: number): Iterable<T>;
1627
1671
 
1672
+ /**
1673
+ * Lazily yields the values in reverse order. Unlike `Array.prototype.reverse`, the source is not changed.
1674
+ * The whole source is read before the first value is yielded, so `reverse` does not end on an infinite source.
1675
+ * Every iteration reads the source again.
1676
+ * @operation `Transformation`
1677
+ * @param iterable - the source `Iterable`
1678
+ * @returns a lazy, re-runnable `Iterable` of the values from the last to the first
1679
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`
1680
+ * @example
1681
+ * ```ts
1682
+ * Array.from(Functions.reverse([1, 2, 3])); // [3, 2, 1]
1683
+ * ```
1684
+ * @since 0.9.0
1685
+ */
1686
+ declare function reverse<T>(iterable: Iterable<T>): Iterable<T>;
1687
+
1628
1688
  /**
1629
1689
  * Tells whether `iterable` and `other` have the same values in the same order.
1630
1690
  * It reads the two sources side by side, and stops and closes both at the first difference or when one ends before the other.
@@ -1711,6 +1771,22 @@ declare function single<T>(iterable: Iterable<T>, predicate: Predicate<T> | unde
1711
1771
  */
1712
1772
  declare function skip<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1713
1773
 
1774
+ /**
1775
+ * Lazily yields every value except the last `count`.
1776
+ * A value is yielded once `count` more values have been read, keeping only those `count` values, so `skipLast` works with infinite sources.
1777
+ * @operation `Transformation`
1778
+ * @param iterable - the source `Iterable`
1779
+ * @param count - how many values to leave out at the end; must be a non-negative integer
1780
+ * @returns a lazy, re-runnable `Iterable` of the values before the last `count`
1781
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`, or if `count` is negative, not an integer, `NaN` or `Infinity`
1782
+ * @example
1783
+ * ```ts
1784
+ * Array.from(Functions.skipLast([1, 2, 3, 4, 5], 2)); // [1, 2, 3]
1785
+ * ```
1786
+ * @since 0.9.0
1787
+ */
1788
+ declare function skipLast<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1789
+
1714
1790
  /**
1715
1791
  * Lazily skips the values while `predicate` returns `true`, then yields the first rejected value and all the rest.
1716
1792
  * After the first rejected value, `predicate` is not called again.
@@ -1824,6 +1900,22 @@ declare function sum<T>(iterable: Iterable<T>, selector: Mapper<T, number> | und
1824
1900
  */
1825
1901
  declare function take<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1826
1902
 
1903
+ /**
1904
+ * Lazily yields the last `count` values.
1905
+ * The whole source is read before the first value is yielded, keeping only the last `count` values, so `takeLast` does not end on an infinite source.
1906
+ * @operation `Transformation`
1907
+ * @param iterable - the source `Iterable`
1908
+ * @param count - how many values to yield; must be a non-negative integer
1909
+ * @returns a lazy, re-runnable `Iterable` of at most `count` values
1910
+ * @throws Error if `iterable` is missing or does not implement `[Symbol.iterator]`, or if `count` is negative, not an integer, `NaN` or `Infinity`
1911
+ * @example
1912
+ * ```ts
1913
+ * Array.from(Functions.takeLast([1, 2, 3, 4, 5], 2)); // [4, 5]
1914
+ * ```
1915
+ * @since 0.9.0
1916
+ */
1917
+ declare function takeLast<T>(iterable: Iterable<T>, count: number): Iterable<T>;
1918
+
1827
1919
  /**
1828
1920
  * Lazily yields the values while a type guard accepts them, narrowing their type, then closes the source.
1829
1921
  * The source is never read past the first rejected value, which is not yielded.