@rimbu/common 0.12.3 → 1.0.0-alpha.1

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.
Files changed (139) hide show
  1. package/README.md +21 -4
  2. package/{src/async-optlazy.ts → dist/bun/async-optlazy.mts} +1 -1
  3. package/{src/async-reducer.ts → dist/bun/async-reducer.mts} +17 -8
  4. package/{src/collect.ts → dist/bun/collect.mts} +1 -1
  5. package/{src/comp.ts → dist/bun/comp.mts} +1 -1
  6. package/{src/index-range.ts → dist/bun/index-range.mts} +1 -1
  7. package/{src/index.ts → dist/bun/index.mts} +1 -1
  8. package/dist/bun/internal.mts +13 -0
  9. package/{src/reducer.ts → dist/bun/reducer.mts} +5 -3
  10. package/dist/cjs/async-optlazy.js +44 -0
  11. package/dist/cjs/async-reducer.js +1621 -0
  12. package/dist/cjs/collect.js +33 -0
  13. package/dist/cjs/comp.js +1621 -0
  14. package/dist/cjs/eq.js +210 -0
  15. package/dist/cjs/err.js +55 -0
  16. package/dist/cjs/index-range.js +114 -0
  17. package/dist/cjs/index.js +1685 -0
  18. package/dist/cjs/internal.js +1685 -0
  19. package/dist/cjs/optlazy.js +41 -0
  20. package/dist/cjs/range.js +52 -0
  21. package/dist/cjs/reducer.js +1621 -0
  22. package/dist/cjs/traverse-state.js +49 -0
  23. package/dist/cjs/types.js +18 -0
  24. package/dist/cjs/update.js +35 -0
  25. package/dist/{module/async-optlazy.js → esm/async-optlazy.mjs} +5 -8
  26. package/dist/esm/async-optlazy.mjs.map +1 -0
  27. package/dist/{module/async-reducer.js → esm/async-reducer.mjs} +67 -69
  28. package/dist/esm/async-reducer.mjs.map +1 -0
  29. package/dist/{module/collect.js → esm/collect.mjs} +1 -1
  30. package/dist/esm/collect.mjs.map +1 -0
  31. package/dist/{module/comp.js → esm/comp.mjs} +2 -2
  32. package/dist/esm/comp.mjs.map +1 -0
  33. package/dist/{module/eq.js → esm/eq.mjs} +1 -1
  34. package/dist/esm/eq.mjs.map +1 -0
  35. package/dist/{module/err.js → esm/err.mjs} +1 -1
  36. package/dist/esm/err.mjs.map +1 -0
  37. package/dist/{module/index-range.js → esm/index-range.mjs} +2 -3
  38. package/dist/esm/index-range.mjs.map +1 -0
  39. package/dist/esm/index.mjs +7 -0
  40. package/dist/esm/index.mjs.map +1 -0
  41. package/dist/esm/internal.mjs +14 -0
  42. package/dist/esm/internal.mjs.map +1 -0
  43. package/dist/{module/optlazy.js → esm/optlazy.mjs} +1 -1
  44. package/dist/esm/optlazy.mjs.map +1 -0
  45. package/dist/{module/range.js → esm/range.mjs} +1 -1
  46. package/dist/esm/range.mjs.map +1 -0
  47. package/dist/{module/reducer.js → esm/reducer.mjs} +10 -8
  48. package/dist/esm/reducer.mjs.map +1 -0
  49. package/dist/{module/traverse-state.js → esm/traverse-state.mjs} +1 -1
  50. package/dist/esm/traverse-state.mjs.map +1 -0
  51. package/dist/esm/types.mjs +2 -0
  52. package/dist/esm/types.mjs.map +1 -0
  53. package/dist/{module/update.js → esm/update.mjs} +1 -1
  54. package/dist/esm/update.mjs.map +1 -0
  55. package/dist/types/{async-optlazy.d.ts → async-optlazy.d.mts} +1 -1
  56. package/dist/types/{async-reducer.d.ts → async-reducer.d.mts} +2 -2
  57. package/dist/types/{collect.d.ts → collect.d.mts} +1 -1
  58. package/dist/types/{comp.d.ts → comp.d.mts} +1 -1
  59. package/dist/types/{index-range.d.ts → index-range.d.mts} +1 -1
  60. package/dist/types/{index.d.ts → index.d.mts} +1 -1
  61. package/dist/types/internal.d.mts +13 -0
  62. package/dist/types/{reducer.d.ts → reducer.d.mts} +1 -1
  63. package/package.json +27 -21
  64. package/src/async-optlazy.mts +49 -0
  65. package/src/async-reducer.mts +1157 -0
  66. package/src/collect.mts +43 -0
  67. package/src/comp.mts +625 -0
  68. package/src/eq.mts +406 -0
  69. package/src/err.mts +43 -0
  70. package/src/index-range.mts +120 -0
  71. package/{dist/module/index.js → src/index.mts} +2 -2
  72. package/src/internal.mts +13 -0
  73. package/{dist/main/optlazy.js → src/optlazy.mts} +24 -14
  74. package/src/range.mts +58 -0
  75. package/src/reducer.mts +1025 -0
  76. package/src/traverse-state.mts +59 -0
  77. package/src/types.mts +40 -0
  78. package/{dist/main/update.js → src/update.mts} +10 -10
  79. package/dist/main/async-optlazy.js +0 -48
  80. package/dist/main/async-optlazy.js.map +0 -1
  81. package/dist/main/async-reducer.js +0 -872
  82. package/dist/main/async-reducer.js.map +0 -1
  83. package/dist/main/collect.js +0 -11
  84. package/dist/main/collect.js.map +0 -1
  85. package/dist/main/comp.js +0 -542
  86. package/dist/main/comp.js.map +0 -1
  87. package/dist/main/eq.js +0 -382
  88. package/dist/main/eq.js.map +0 -1
  89. package/dist/main/err.js +0 -62
  90. package/dist/main/err.js.map +0 -1
  91. package/dist/main/index-range.js +0 -110
  92. package/dist/main/index-range.js.map +0 -1
  93. package/dist/main/index.js +0 -10
  94. package/dist/main/index.js.map +0 -1
  95. package/dist/main/internal.js +0 -17
  96. package/dist/main/internal.js.map +0 -1
  97. package/dist/main/optlazy.js.map +0 -1
  98. package/dist/main/range.js +0 -38
  99. package/dist/main/range.js.map +0 -1
  100. package/dist/main/reducer.js +0 -694
  101. package/dist/main/reducer.js.map +0 -1
  102. package/dist/main/traverse-state.js +0 -33
  103. package/dist/main/traverse-state.js.map +0 -1
  104. package/dist/main/types.js +0 -3
  105. package/dist/main/types.js.map +0 -1
  106. package/dist/main/update.js.map +0 -1
  107. package/dist/module/async-optlazy.js.map +0 -1
  108. package/dist/module/async-reducer.js.map +0 -1
  109. package/dist/module/collect.js.map +0 -1
  110. package/dist/module/comp.js.map +0 -1
  111. package/dist/module/eq.js.map +0 -1
  112. package/dist/module/err.js.map +0 -1
  113. package/dist/module/index-range.js.map +0 -1
  114. package/dist/module/index.js.map +0 -1
  115. package/dist/module/internal.js +0 -14
  116. package/dist/module/internal.js.map +0 -1
  117. package/dist/module/optlazy.js.map +0 -1
  118. package/dist/module/range.js.map +0 -1
  119. package/dist/module/reducer.js.map +0 -1
  120. package/dist/module/traverse-state.js.map +0 -1
  121. package/dist/module/types.js +0 -2
  122. package/dist/module/types.js.map +0 -1
  123. package/dist/module/update.js.map +0 -1
  124. package/dist/types/internal.d.ts +0 -13
  125. package/src/internal.ts +0 -13
  126. /package/{src/eq.ts → dist/bun/eq.mts} +0 -0
  127. /package/{src/err.ts → dist/bun/err.mts} +0 -0
  128. /package/{src/optlazy.ts → dist/bun/optlazy.mts} +0 -0
  129. /package/{src/range.ts → dist/bun/range.mts} +0 -0
  130. /package/{src/traverse-state.ts → dist/bun/traverse-state.mts} +0 -0
  131. /package/{src/types.ts → dist/bun/types.mts} +0 -0
  132. /package/{src/update.ts → dist/bun/update.mts} +0 -0
  133. /package/dist/types/{eq.d.ts → eq.d.mts} +0 -0
  134. /package/dist/types/{err.d.ts → err.d.mts} +0 -0
  135. /package/dist/types/{optlazy.d.ts → optlazy.d.mts} +0 -0
  136. /package/dist/types/{range.d.ts → range.d.mts} +0 -0
  137. /package/dist/types/{traverse-state.d.ts → traverse-state.d.mts} +0 -0
  138. /package/dist/types/{types.d.ts → types.d.mts} +0 -0
  139. /package/dist/types/{update.d.ts → update.d.mts} +0 -0
@@ -0,0 +1,1025 @@
1
+ import { CollectFun, Eq, OptLazy } from './internal.mjs';
2
+
3
+ /**
4
+ * A `Reducer` is a stand-alone calculation that takes input values of type I, and, when requested, produces an output value of type O.
5
+ * @typeparam I - the input value type
6
+ * @typeparam O - the output value type
7
+ */
8
+ export type Reducer<I, O = I> = Reducer.Impl<I, O, unknown>;
9
+
10
+ function identity<T>(value: T): T {
11
+ return value;
12
+ }
13
+
14
+ export namespace Reducer {
15
+ /**
16
+ * The Implementation interface for a `Reducer`, which also exposes the internal state type.
17
+ * @typeparam I - the input value type
18
+ * @typeparam O - the output value type
19
+ * @typeparam S - the internal state type
20
+ */
21
+ export interface Impl<I, O, S> {
22
+ /**
23
+ * The initial state value for the reducer algorithm.
24
+ */
25
+ readonly init: OptLazy<S>;
26
+ /**
27
+ * Returns the next state based on the given input values
28
+ * @param state - the current state
29
+ * @param elem - the current input value
30
+ * @param index - the current input index
31
+ * @param halt - a function that, when called, ensures no more values are passed to the reducer
32
+ */
33
+ next(state: S, elem: I, index: number, halt: () => void): S;
34
+ /**
35
+ * Returns the output value based on the given `state`
36
+ * @param state - the current state
37
+ */
38
+ stateToResult(state: S): O;
39
+ /**
40
+ * Returns a `Reducer` instance that only passes values to the reducer that satisy the given `pred` predicate.
41
+ * @param pred - a function that returns true if the value should be passed to the reducer based on the following inputs:<br/>
42
+ * - value: the current input value<br/>
43
+ * - index: the current input index<br/>
44
+ * - halt: function that, when called, ensures no more new values are passed to the reducer
45
+ * @example
46
+ * ```ts
47
+ * Reducer.sum.filterInput(v => v > 10)
48
+ * // this reducer will only sum values larger than 10
49
+ * ```
50
+ */
51
+ filterInput(
52
+ pred: (value: I, index: number, halt: () => void) => boolean
53
+ ): Reducer<I, O>;
54
+ /**
55
+ * Returns a `Reducer` instance that converts its input values using given `mapFun` before passing them to the reducer.
56
+ * @typeparam I2 - the resulting reducer input type
57
+ * @param mapFun - a function that returns a new value to pass to the reducer based on the following inputs:<br/>
58
+ * - value: the current input value<br/>
59
+ * - index: the current input index
60
+ * @example
61
+ * ```ts
62
+ * Reducer.sum.mapInput(v => v * 2)
63
+ * // this reducer will double all input values before summing them
64
+ * ```
65
+ */
66
+ mapInput<I2>(mapFun: (value: I2, index: number) => I): Reducer<I2, O>;
67
+ /**
68
+ * Returns a `Reducer` instance that converts or filters its input values using given `collectFun` before passing them to the reducer.
69
+ * @typeparam I2 - the resulting reducer input type
70
+ * @param collectFun - a function receiving<br/>
71
+ * - `value`: the next value<br/>
72
+ * - `index`: the value index<br/>
73
+ * - `skip`: a token that, when returned, will not add a value to the resulting collection<br/>
74
+ * - `halt`: a function that, when called, ensures no next elements are passed
75
+ * @example
76
+ * ```ts
77
+ * Reducer.sum.collectInput((v, _, skip) => v <= 10 ? skip : v * 2)
78
+ * // this reducer will double all input values larger thant 10 before summing them,
79
+ * // and will skip all values smaller than 10
80
+ * ```
81
+ */
82
+ collectInput<I2>(collectFun: CollectFun<I2, I>): Reducer<I2, O>;
83
+ /**
84
+ * Returns a `Reducer` instance that converts its output values using given `mapFun`.
85
+ * @typeparam O2 - the resulting reducer output type
86
+ * @param mapFun - a function that takes the current output value and converts it to a new output value
87
+ * @example
88
+ * ```ts
89
+ * Reducer.sum.mapOutput(String)
90
+ * // this reducer will convert all its results to string before returning them
91
+ * ```
92
+ */
93
+ mapOutput<O2>(mapFun: (value: O) => O2): Reducer<I, O2>;
94
+ /**
95
+ * Returns a `Reducer` instance that takes at most the given `amount` of input elements, and will ignore subsequent elements.
96
+ * @param amount - the amount of elements to accept
97
+ * @example
98
+ * ```ts
99
+ * Stream.range({ end: 10 }).reduce(Reducer.sum.takeInput(2))
100
+ * // => 1
101
+ * ```
102
+ */
103
+ takeInput(amount: number): Reducer<I, O>;
104
+ /**
105
+ * Returns a `Reducer` instance that skips the first given `amount` of input elements, and will process subsequent elements.
106
+ * @param amount - the amount of elements to skip
107
+ * @example
108
+ * ```ts
109
+ * Stream.range({ end: 10 }).reduce(Reducer.sum.dropInput(9))
110
+ * // => 19
111
+ * ```
112
+ */
113
+ dropInput(amount: number): Reducer<I, O>;
114
+ /**
115
+ * Returns a `Reducer` instance that takes given `amount` of elements starting at given `from` index, and ignores other elements.
116
+ * @param from - (default: 0) the index at which to start processing elements
117
+ * @param amount - (optional) the amount of elements to process, if not given, processes all elements from the `from` index
118
+ * @example
119
+ * ```ts
120
+ * Stream.range({ end: 10 }).reduce(Reducer.sum.sliceInput(1, 2))
121
+ * // => 3
122
+ * ```
123
+ */
124
+ sliceInput(from?: number, amount?: number): Reducer<I, O>;
125
+ }
126
+
127
+ /**
128
+ * A base class that can be used to easily create `Reducer` instances.
129
+ * @typeparam I - the input value type
130
+ * @typeparam O - the output value type
131
+ * @typeparam S - the internal state type
132
+ */
133
+ export class Base<I, O, S> implements Reducer.Impl<I, O, S> {
134
+ constructor(
135
+ readonly init: OptLazy<S>,
136
+ readonly next: (state: S, elem: I, index: number, halt: () => void) => S,
137
+ readonly stateToResult: (state: S) => O
138
+ ) {}
139
+
140
+ filterInput(
141
+ pred: (value: I, index: number, halt: () => void) => boolean
142
+ ): Reducer<I, O> {
143
+ return create(
144
+ (): { state: S; nextIndex: number } => ({
145
+ nextIndex: 0,
146
+ state: OptLazy(this.init),
147
+ }),
148
+ (state, elem, index, halt): { state: S; nextIndex: number } => {
149
+ if (pred(elem, index, halt)) {
150
+ state.state = this.next(state.state, elem, state.nextIndex++, halt);
151
+ }
152
+ return state;
153
+ },
154
+ (state): O => this.stateToResult(state.state)
155
+ );
156
+ }
157
+
158
+ mapInput<I2>(mapFun: (value: I2, index: number) => I): Reducer<I2, O> {
159
+ return create(
160
+ this.init,
161
+ (state, elem, index, halt): S =>
162
+ this.next(state, mapFun(elem, index), index, halt),
163
+ this.stateToResult
164
+ );
165
+ }
166
+
167
+ collectInput<I2>(collectFun: CollectFun<I2, I>): Reducer<I2, O> {
168
+ return create(
169
+ (): { state: S; nextIndex: number } => ({
170
+ nextIndex: 0,
171
+ state: OptLazy(this.init),
172
+ }),
173
+ (state, elem, index, halt): { state: S; nextIndex: number } => {
174
+ const nextElem = collectFun(elem, index, CollectFun.Skip, halt);
175
+
176
+ if (CollectFun.Skip !== nextElem) {
177
+ state.state = this.next(
178
+ state.state,
179
+ nextElem,
180
+ state.nextIndex++,
181
+ halt
182
+ );
183
+ }
184
+
185
+ return state;
186
+ },
187
+ (state): O => this.stateToResult(state.state)
188
+ );
189
+ }
190
+
191
+ mapOutput<O2>(mapFun: (value: O) => O2): Reducer<I, O2> {
192
+ return create(
193
+ this.init,
194
+ this.next,
195
+ (state): O2 => mapFun(this.stateToResult(state))
196
+ );
197
+ }
198
+
199
+ takeInput(amount: number): Reducer<I, O> {
200
+ if (amount <= 0) {
201
+ return create(this.init, identity, this.stateToResult);
202
+ }
203
+
204
+ return this.filterInput((_, i, halt): boolean => {
205
+ const more = i < amount;
206
+ if (!more) halt();
207
+ return more;
208
+ });
209
+ }
210
+
211
+ dropInput(amount: number): Reducer<I, O> {
212
+ if (amount <= 0) return this;
213
+
214
+ return this.filterInput((_, i): boolean => i >= amount);
215
+ }
216
+
217
+ sliceInput(from = 0, amount?: number): Reducer<I, O> {
218
+ if (undefined === amount) return this.dropInput(from);
219
+ if (amount <= 0) return create(this.init, identity, this.stateToResult);
220
+ if (from <= 0) return this.takeInput(amount);
221
+ return this.takeInput(amount).dropInput(from);
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Returns a `Reducer` with the given options:
227
+ * @param init - the initial state value
228
+ * @param next - returns the next state value based on the given inputs:<br/>
229
+ * - current: the current state<br/>
230
+ * - next: the current input value<br/>
231
+ * - index: the input index value<br/>
232
+ * - halt: function that, when called, ensures no more elements are passed to the reducer
233
+ * @param stateToResult - a function that converts the current state to an output value
234
+ * @typeparam I - the input value type
235
+ * @typeparam O - the output value type
236
+ * @typeparam S - the internal state type
237
+ * @example
238
+ * ```ts
239
+ * const evenNumberOfOnes = Reducer
240
+ * .create(
241
+ * true,
242
+ * (current, value: number) => value === 1 ? !current : current,
243
+ * state => state ? 'even' : 'not even')
244
+ * const result = Stream.of(1, 2, 3, 2, 1)).reduce(evenNumberOfOnes)
245
+ * console.log+(result)
246
+ * // => 'even'
247
+ * ```
248
+ */
249
+ export function create<I, O = I, S = O>(
250
+ init: OptLazy<S>,
251
+ next: (current: S, next: I, index: number, halt: () => void) => S,
252
+ stateToResult: (state: S) => O
253
+ ): Reducer<I, O> {
254
+ return new Reducer.Base(init, next, stateToResult);
255
+ }
256
+
257
+ /**
258
+ * Returns a `Reducer` of which the input, state, and output types are the same.
259
+ * @param init - the initial state value
260
+ * @param next - returns the next state value based on the given inputs:<br/>
261
+ * - current: the current state<br/>
262
+ * - next: the current input value<br/>
263
+ * - index: the input index value<br/>
264
+ * - halt: function that, when called, ensures no more elements are passed to the reducer
265
+ * @param stateToResult - (optional) a function that converts the current state to an output value
266
+ * @typeparam T - the overall value type
267
+ * @example
268
+ * ```ts
269
+ * const sum = Reducer
270
+ * .createMono(
271
+ * 0,
272
+ * (current, value) => current + value
273
+ * )
274
+ * const result = Stream.of(1, 2, 3, 2, 1)).reduce(sum)
275
+ * console.log+(result)
276
+ * // => 9
277
+ * ```
278
+ */
279
+ export function createMono<T>(
280
+ init: OptLazy<T>,
281
+ next: (current: T, next: T, index: number, halt: () => void) => T,
282
+ stateToResult?: (state: T) => T
283
+ ): Reducer<T> {
284
+ return create(init, next, stateToResult ?? identity);
285
+ }
286
+
287
+ /**
288
+ * Returns a `Reducer` of which the state and output types are the same.
289
+ * @param init - the initial state value
290
+ * @param next - returns the next state value based on the given inputs:<br/>
291
+ * - current: the current state<br/>
292
+ * - next: the current input value<br/>
293
+ * - index: the input index value<br/>
294
+ * - halt: function that, when called, ensures no more elements are passed to the reducer
295
+ * @param stateToResult - (optional) a function that converts the current state to an output value
296
+ * @typeparam I - the input value type
297
+ * @typeparam O - the output value type
298
+ * @example
299
+ * ```ts
300
+ * const boolToString = Reducer
301
+ * .createOutput(
302
+ * '',
303
+ * (current, value: boolean) => current + (value ? 'T' : 'F')
304
+ * )
305
+ * const result = Stream.of(true, false, true)).reduce(boolToString)
306
+ * console.log+(result)
307
+ * // => 'TFT'
308
+ * ```
309
+ */
310
+ export function createOutput<I, O = I>(
311
+ init: OptLazy<O>,
312
+ next: (current: O, next: I, index: number, halt: () => void) => O,
313
+ stateToResult?: (state: O) => O
314
+ ): Reducer<I, O> {
315
+ return create(init, next, stateToResult ?? identity);
316
+ }
317
+
318
+ /**
319
+ * A `Reducer` that sums all given numeric input values.
320
+ * @example
321
+ * ```ts
322
+ * console.log(Stream.range({ amount: 5 }).reduce(Reducer.sum))
323
+ * // => 10
324
+ * ```
325
+ */
326
+ export const sum = createMono(0, (state, next): number => state + next);
327
+
328
+ /**
329
+ * A `Reducer` that calculates the product of all given numeric input values.
330
+ * @example
331
+ * ```ts
332
+ * console.log(Stream.range({ start: 1, amount: 5 }).reduce(product))
333
+ * // => 120
334
+ * ```
335
+ */
336
+ export const product = createMono(1, (state, next, _, halt): number => {
337
+ if (0 === next) halt();
338
+ return state * next;
339
+ });
340
+
341
+ /**
342
+ * A `Reducer` that calculates the average of all given numberic input values.
343
+ * @example
344
+ * ```ts
345
+ * console.log(Stream.range({ amount: 5 }).reduce(Reducer.average));
346
+ * // => 2
347
+ * ```
348
+ */
349
+ export const average = createMono(
350
+ 0,
351
+ (avg, value, index): number => avg + (value - avg) / (index + 1)
352
+ );
353
+
354
+ /**
355
+ * Returns a `Reducer` that remembers the minimum value of the inputs using the given `compFun` to compare input values
356
+ * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller
357
+ * @param otherwise - (default: undefineds) a fallback value when there were no input values given
358
+ * @example
359
+ * ```ts
360
+ * const stream = Stream.of('abc', 'a', 'abcde', 'ab')
361
+ * console.log(stream.minBy((s1, s2) => s1.length - s2.length))
362
+ * // 'a'
363
+ * ```
364
+ */
365
+ export const minBy: {
366
+ <T>(compFun: (v1: T, v2: T) => number): Reducer<T, T | undefined>;
367
+ <T, O>(compFun: (v1: T, v2: T) => number, otherwise: OptLazy<O>): Reducer<
368
+ T,
369
+ T | O
370
+ >;
371
+ } = <T, O>(compFun: (v1: T, v2: T) => number, otherwise?: OptLazy<O>) => {
372
+ const token = Symbol();
373
+ return create<T, T | O, T | typeof token>(
374
+ token,
375
+ (state, next): T => {
376
+ if (token === state) return next;
377
+ return compFun(state, next) < 0 ? state : next;
378
+ },
379
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
380
+ );
381
+ };
382
+
383
+ /**
384
+ * Returns a `Reducer` that remembers the minimum value of the numberic inputs.
385
+ * @param otherwise - (default: undefined) a fallback value when there were no input values given
386
+ * @example
387
+ * ```ts
388
+ * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.min()))
389
+ * // => 3
390
+ * ```
391
+ */
392
+ // prettier-ignore
393
+ export const min: {
394
+ (): Reducer<number, number | undefined>;
395
+ <O>(otherwise: OptLazy<O>): Reducer<number, number | O>;
396
+ } = <O,>(otherwise?: OptLazy<O>) => {
397
+ return create<number, number | O, number | undefined>(
398
+ undefined,
399
+ (state, next): number =>
400
+ undefined !== state && state < next ? state : next,
401
+ (state): number | O => state ?? OptLazy(otherwise!)
402
+ );
403
+ };
404
+
405
+ /**
406
+ * Returns a `Reducer` that remembers the maximum value of the inputs using the given `compFun` to compare input values
407
+ * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller
408
+ * @param otherwise - (default: undefined) a fallback value when there were no input values given
409
+ * @example
410
+ * ```ts
411
+ * const stream = Stream.of('abc', 'a', 'abcde', 'ab')
412
+ * console.log(stream.maxBy((s1, s2) => s1.length - s2.length))
413
+ * // 'abcde'
414
+ * ```
415
+ */
416
+ export const maxBy: {
417
+ <T>(compFun: (v1: T, v2: T) => number): Reducer<T, T | undefined>;
418
+ <T, O>(compFun: (v1: T, v2: T) => number, otherwise: OptLazy<O>): Reducer<
419
+ T,
420
+ T | O
421
+ >;
422
+ } = <T, O>(
423
+ compFun: (v1: T, v2: T) => number,
424
+ otherwise?: OptLazy<O>
425
+ ): Reducer<T, T | O> => {
426
+ const token = Symbol();
427
+ return create<T, T | O, T | typeof token>(
428
+ token,
429
+ (state, next): T => {
430
+ if (token === state) return next;
431
+ return compFun(state, next) > 0 ? state : next;
432
+ },
433
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
434
+ );
435
+ };
436
+
437
+ /**
438
+ * Returns a `Reducer` that remembers the maximum value of the numberic inputs.
439
+ * @param otherwise - (default: undefined) a fallback value when there were no input values given
440
+ * @example
441
+ * ```ts
442
+ * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.max()))
443
+ * // => 7
444
+ * ```
445
+ */
446
+ // prettier-ignore
447
+ export const max: {
448
+ (): Reducer<number, number | undefined>;
449
+ <O>(otherwise: OptLazy<O>): Reducer<number, number | O>;
450
+ } = <O,>(otherwise?: OptLazy<O>): Reducer<number, number | O> => {
451
+ return create<number, number | O, number | undefined>(
452
+ undefined,
453
+ (state, next): number =>
454
+ undefined !== state && state > next ? state : next,
455
+ (state): number | O => state ?? OptLazy(otherwise!)
456
+ );
457
+ };
458
+
459
+ /**
460
+ * Returns a `Reducer` that joins the given input values into a string using the given options.
461
+ * @param options - an object containing:<br/>
462
+ * - sep: (optional) a seperator string value between values in the output<br/>
463
+ * - start: (optional) a start string to prepend to the output<br/>
464
+ * - end: (optional) an end string to append to the output<br/>
465
+ * @example
466
+ * ```ts
467
+ * console.log(Stream.of(1, 2, 3).reduce(Reducer.join({ sep: '-' })))
468
+ * // => '1-2-3'
469
+ * ```
470
+ */
471
+ export function join<T>({
472
+ sep = '',
473
+ start = '',
474
+ end = '',
475
+ valueToString = String as (value: T) => string,
476
+ } = {}): Reducer<T, string> {
477
+ let curSep = '';
478
+ let curStart = start;
479
+ return create(
480
+ '',
481
+ (state, next): string => {
482
+ const result = curStart.concat(state, curSep, valueToString(next));
483
+ curSep = sep;
484
+ curStart = '';
485
+ return result;
486
+ },
487
+ (state): string => state.concat(end)
488
+ );
489
+ }
490
+
491
+ /**
492
+ * Returns a `Reducer` that remembers the amount of input items provided.
493
+ * @param pred - (optional) a predicate that returns false if the item should not be counted given:<br/>
494
+ * - value: the current input value<br/>
495
+ * - index: the input value index
496
+ * @example
497
+ * ```ts
498
+ * const stream = Stream.range({ amount: 10 })
499
+ * console.log(stream.reduce(Reducer.count()))
500
+ * // => 10
501
+ * console.log(stream.reduce(Reducer.count(v => v < 5)))
502
+ * // => 5
503
+ * ```
504
+ */
505
+ export const count: {
506
+ (): Reducer<never, number>;
507
+ <T>(pred: (value: T, index: number) => boolean): Reducer<T, number>;
508
+ } = (
509
+ pred?: (value: unknown, index: number) => boolean
510
+ ): Reducer<never, number> => {
511
+ if (undefined === pred) return createOutput(0, (_, __, i): number => i + 1);
512
+
513
+ return createOutput(0, (state, next, i): number => {
514
+ if (pred?.(next, i)) return state + 1;
515
+ return state;
516
+ });
517
+ };
518
+
519
+ /**
520
+ * Returns a `Reducer` that remembers the first input value for which the given `pred` function returns true.
521
+ * @param pred - a function taking an input value and its index, and returning true if the value should be remembered
522
+ * @param otherwise - (default: undefined) a fallback value to output if no input value yet has satisfied the given predicate
523
+ * @typeparam T - the input value type
524
+ * @typeparam O - the fallback value type
525
+ * @example
526
+ * ```ts
527
+ * console.log(Stream.range({ amount: 10 }).reduce(Reducer.firstWhere(v => v > 5)))
528
+ * // => 6
529
+ * ```
530
+ */
531
+ export const firstWhere: {
532
+ <T>(pred: (value: T, index: number) => boolean): Reducer<T, T | undefined>;
533
+ <T, O>(
534
+ pred: (value: T, index: number) => boolean,
535
+ otherwise: OptLazy<O>
536
+ ): Reducer<T, T | O>;
537
+ } = <T, O>(
538
+ pred: (value: T, index: number) => boolean,
539
+ otherwise?: OptLazy<O>
540
+ ) => {
541
+ const token = Symbol();
542
+
543
+ return create<T, T | O, T | typeof token>(
544
+ token,
545
+ (state, next, i, halt): T | typeof token => {
546
+ if (token === state && pred(next, i)) {
547
+ halt();
548
+ return next;
549
+ }
550
+ return state;
551
+ },
552
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
553
+ );
554
+ };
555
+
556
+ /**
557
+ * Returns a `Reducer` that remembers the first input value.
558
+ * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided
559
+ * @typeparam T - the input value type
560
+ * @typeparam O - the fallback value type
561
+ * @example
562
+ * ```ts
563
+ * console.log(Stream.range{ amount: 10 }).reduce(Reducer.first())
564
+ * // => 0
565
+ * ```
566
+ */
567
+ export const first: {
568
+ <T>(): Reducer<T, T | undefined>;
569
+ <T, O>(otherwise: OptLazy<O>): Reducer<T, T | O>;
570
+ } = <T, O>(otherwise?: OptLazy<O>): Reducer<T, T | O> => {
571
+ const token = Symbol();
572
+
573
+ return create<T, T | O, T | typeof token>(
574
+ token,
575
+ (state, next, _, halt): T => {
576
+ halt();
577
+ if (token === state) return next;
578
+ return state;
579
+ },
580
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
581
+ );
582
+ };
583
+
584
+ /**
585
+ * Returns a `Reducer` that remembers the last input value for which the given `pred` function returns true.
586
+ * @param pred - a function taking an input value and its index, and returning true if the value should be remembered
587
+ * @param otherwise - (default: undefined) a fallback value to output if no input value yet has satisfied the given predicate
588
+ * @typeparam T - the input value type
589
+ * @typeparam O - the fallback value type
590
+ * @example
591
+ * ```ts
592
+ * console.log(Stream.range({ amount: 10 }).reduce(Reducer.lastWhere(v => v > 5)))
593
+ * // => 9
594
+ * ```
595
+ */
596
+ export const lastWhere: {
597
+ <T>(pred: (value: T, index: number) => boolean): Reducer<T, T | undefined>;
598
+ <T, O>(
599
+ pred: (value: T, index: number) => boolean,
600
+ otherwise: OptLazy<O>
601
+ ): Reducer<T, T | O>;
602
+ } = <T, O>(
603
+ pred: (value: T, index: number) => boolean,
604
+ otherwise?: OptLazy<O>
605
+ ) => {
606
+ const token = Symbol();
607
+
608
+ return create<T, T | O, T | typeof token>(
609
+ token,
610
+ (state, next, i): T | typeof token => {
611
+ if (pred(next, i)) return next;
612
+ return state;
613
+ },
614
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
615
+ );
616
+ };
617
+
618
+ /**
619
+ * Returns a `Reducer` that remembers the last input value.
620
+ * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided
621
+ * @typeparam T - the input value type
622
+ * @typeparam O - the fallback value type
623
+ * @example
624
+ * ```ts
625
+ * console.log(Stream.range{ amount: 10 }).reduce(Reducer.last())
626
+ * // => 9
627
+ * ```
628
+ */
629
+ export const last: {
630
+ <T>(): Reducer<T, T | undefined>;
631
+ <T, O>(otherwise: OptLazy<O>): Reducer<T, T | O>;
632
+ } = <T, O>(otherwise?: OptLazy<O>): Reducer<T, T | O> => {
633
+ const token = Symbol();
634
+
635
+ return create<T, T | O, T | typeof token>(
636
+ () => token,
637
+ (_, next): T => next,
638
+ (state): T | O => (token === state ? OptLazy(otherwise!) : state)
639
+ );
640
+ };
641
+
642
+ /**
643
+ * Returns a `Reducer` that ouputs false as long as no input value satisfies given `pred`, true otherwise.
644
+ * @typeparam T - the element type
645
+ * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate
646
+ * @example
647
+ * ```ts
648
+ * console.log(Stream.range{ amount: 10 }).reduce(Reducer.some(v => v > 5))
649
+ * // => true
650
+ * ```
651
+ */
652
+ export function some<T>(
653
+ pred: (value: T, index: number) => boolean
654
+ ): Reducer<T, boolean> {
655
+ return createOutput<T, boolean>(false, (state, next, i, halt): boolean => {
656
+ if (state) return state;
657
+ const satisfies = pred(next, i);
658
+
659
+ if (satisfies) {
660
+ halt();
661
+ }
662
+
663
+ return satisfies;
664
+ });
665
+ }
666
+
667
+ /**
668
+ * Returns a `Reducer` that ouputs true as long as all input values satisfy the given `pred`, false otherwise.
669
+ * @typeparam T - the element type
670
+ * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate
671
+ * @example
672
+ * ```ts
673
+ * console.log(Stream.range{ amount: 10 }).reduce(Reducer.every(v => v < 5))
674
+ * // => false
675
+ * ```
676
+ */
677
+ export function every<T>(
678
+ pred: (value: T, index: number) => boolean
679
+ ): Reducer<T, boolean> {
680
+ return createOutput<T, boolean>(true, (state, next, i, halt): boolean => {
681
+ if (!state) return state;
682
+
683
+ const satisfies = pred(next, i);
684
+
685
+ if (!satisfies) {
686
+ halt();
687
+ }
688
+
689
+ return satisfies;
690
+ });
691
+ }
692
+
693
+ /**
694
+ * Returns a `Reducer` that outputs false as long as the given `elem` has not been encountered in the input values, true otherwise.
695
+ * @typeparam T - the element type
696
+ * @param elem - the element to search for
697
+ * @param eq - (optional) a comparison function that returns true if te two given input values are considered equal
698
+ * @example
699
+ * ```ts
700
+ * console.log(Stream.range({ amount: 10 }).reduce(Reducer.contains(5)))
701
+ * // => true
702
+ * ```
703
+ */
704
+ export function contains<T>(
705
+ elem: T,
706
+ eq: Eq<T> = Object.is
707
+ ): Reducer<T, boolean> {
708
+ return createOutput<T, boolean>(false, (state, next, _, halt): boolean => {
709
+ if (state) return state;
710
+ const satisfies = eq(next, elem);
711
+
712
+ if (satisfies) {
713
+ halt();
714
+ }
715
+
716
+ return satisfies;
717
+ });
718
+ }
719
+
720
+ /**
721
+ * Returns a `Reducer` that takes boolean values and outputs true if all input values are true, and false otherwise.
722
+ * @example
723
+ * ```ts
724
+ * console.log(Stream.of(true, false, true)).reduce(Reducer.and))
725
+ * // => false
726
+ * ```
727
+ */
728
+ export const and = createMono(true, (state, next, _, halt): boolean => {
729
+ if (!state) return state;
730
+ if (!next) halt();
731
+ return next;
732
+ });
733
+
734
+ /**
735
+ * Returns a `Reducer` that takes boolean values and outputs true if one or more input values are true, and false otherwise.
736
+ * @example
737
+ * ```ts
738
+ * console.log(Stream.of(true, false, true)).reduce(Reducer.or))
739
+ * // => true
740
+ * ```
741
+ */
742
+ export const or = createMono(false, (state, next, _, halt): boolean => {
743
+ if (state) return state;
744
+ if (next) halt();
745
+ return next;
746
+ });
747
+
748
+ /**
749
+ * Returns a `Reducer` that outputs true if no input values are received, false otherwise.
750
+ * @example
751
+ * ```ts
752
+ * console.log(Stream.of(1, 2, 3).reduce(Reducer.isEmpty))
753
+ * // => false
754
+ * ```
755
+ */
756
+ export const isEmpty = createOutput<never, boolean>(
757
+ true,
758
+ (_, __, ___, halt): false => {
759
+ halt();
760
+ return false;
761
+ }
762
+ );
763
+
764
+ /**
765
+ * Returns a `Reducer` that outputs true if one or more input values are received, false otherwise.
766
+ * @example
767
+ * ```ts
768
+ * console.log(Stream.of(1, 2, 3).reduce(Reducer.nonEmpty))
769
+ * // => true
770
+ * ```
771
+ */
772
+ export const nonEmpty = createOutput<never, boolean>(
773
+ false,
774
+ (_, __, ___, halt): true => {
775
+ halt();
776
+ return true;
777
+ }
778
+ );
779
+
780
+ /**
781
+ * Returns a `Reducer` that collects received input values in an array, and returns a copy of that array as an output value when requested.
782
+ * @typeparam T - the element type
783
+ * @example
784
+ * ```ts
785
+ * console.log(Stream.of(1, 2, 3).reduce(Reducer.toArray()))
786
+ * // => [1, 2, 3]
787
+ * ```
788
+ */
789
+ export function toArray<T>(): Reducer<T, T[]> {
790
+ return create(
791
+ (): T[] => [],
792
+ (state, next): T[] => {
793
+ state.push(next);
794
+ return state;
795
+ },
796
+ (state): T[] => state.slice()
797
+ );
798
+ }
799
+
800
+ /**
801
+ * Returns a `Reducer` that collects received input tuples into a mutable JS Map, and returns
802
+ * a copy of that map when output is requested.
803
+ * @typeparam K - the map key type
804
+ * @typeparam V - the map value type
805
+ * @example
806
+ * ```ts
807
+ * console.log(Stream.of([1, 'a'], [2, 'b']).reduce(Reducer.toJSMap()))
808
+ * // Map { 1 => 'a', 2 => 'b' }
809
+ * ```
810
+ */
811
+ export function toJSMap<K, V>(): Reducer<[K, V], Map<K, V>> {
812
+ return create(
813
+ (): Map<K, V> => new Map(),
814
+ (state, next): Map<K, V> => {
815
+ state.set(next[0], next[1]);
816
+ return state;
817
+ },
818
+ (s): Map<K, V> => new Map(s)
819
+ );
820
+ }
821
+
822
+ /**
823
+ * Returns a `Reducer` that collects received input values into a mutable JS Set, and returns
824
+ * a copy of that map when output is requested.
825
+ * @typeparam T - the element type
826
+ * @example
827
+ * ```ts
828
+ * console.log(Stream.of(1, 2, 3).reduce(Reducer.toJSSet()))
829
+ * // Set {1, 2, 3}
830
+ * ```
831
+ */
832
+ export function toJSSet<T>(): Reducer<T, Set<T>> {
833
+ return create(
834
+ (): Set<T> => new Set<T>(),
835
+ (state, next): Set<T> => {
836
+ state.add(next);
837
+ return state;
838
+ },
839
+ (s): Set<T> => new Set(s)
840
+ );
841
+ }
842
+
843
+ /**
844
+ * Returns a `Reducer` that collects 2-tuples containing keys and values into a plain JS object, and
845
+ * returns a copy of that object when output is requested.
846
+ * @typeparam K - the result object key type
847
+ * @typeparam V - the result object value type
848
+ * @example
849
+ * ```ts
850
+ * console.log(Stream.of(['a', 1], ['b', true]).reduce(Reducer.toJSObject()))
851
+ * // { a: 1, b: true }
852
+ * ```
853
+ */
854
+ export function toJSObject<K extends string | number | symbol, V>(): Reducer<
855
+ [K, V],
856
+ Record<K, V>
857
+ > {
858
+ return create(
859
+ () => ({} as Record<K, V>),
860
+ (state, entry) => {
861
+ state[entry[0]] = entry[1];
862
+ return state;
863
+ },
864
+ (s) => ({ ...s })
865
+ );
866
+ }
867
+
868
+ /**
869
+ * Returns a `Reducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in an array.
870
+ * @param reducers - 2 or more reducers to combine
871
+ * @example
872
+ * ```ts
873
+ * const red = Reducer.combineArr(Reducer.sum, Reducer.average)
874
+ * console.log(Stream.range({amount: 9 }).reduce(red))
875
+ * // => [36, 4]
876
+ * ```
877
+ */
878
+ export function combineArr<
879
+ T,
880
+ R extends readonly [unknown, unknown, ...unknown[]]
881
+ >(
882
+ ...reducers: { [K in keyof R]: Reducer<T, R[K]> } & Reducer<T, unknown>[]
883
+ ): Reducer<T, R> {
884
+ const createState = (): {
885
+ reducer: Reducer<T, unknown>;
886
+ halted: boolean;
887
+ halt(): void;
888
+ state: unknown;
889
+ }[] => {
890
+ return reducers.map((reducer) => {
891
+ const result = {
892
+ reducer,
893
+ halted: false,
894
+ halt(): void {
895
+ result.halted = true;
896
+ },
897
+ state: OptLazy(reducer.init),
898
+ };
899
+
900
+ return result;
901
+ });
902
+ };
903
+
904
+ return create<T, R, ReturnType<typeof createState>>(
905
+ createState,
906
+ (allState, next, index, halt) => {
907
+ let anyNotHalted = false;
908
+
909
+ let i = -1;
910
+ const len = allState.length;
911
+
912
+ while (++i < len) {
913
+ const red = allState[i];
914
+
915
+ if (red.halted) {
916
+ continue;
917
+ }
918
+
919
+ red.state = red.reducer.next(red.state, next, index, red.halt);
920
+
921
+ if (!red.halted) {
922
+ anyNotHalted = true;
923
+ }
924
+ }
925
+
926
+ if (!anyNotHalted) {
927
+ halt();
928
+ }
929
+
930
+ return allState;
931
+ },
932
+ (allState) =>
933
+ allState.map((st) => st.reducer.stateToResult(st.state)) as any
934
+ );
935
+ }
936
+
937
+ /**
938
+ * Returns a `Reducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in the shape of the given object.
939
+ * @typeparam T - the input type for all the reducers
940
+ * @typeparam R - the result object shape
941
+ * @param reducerObj - an object of keys, and reducers corresponding to those keys
942
+ * @example
943
+ * ```ts
944
+ * const red = Reducer.combineObj({
945
+ * theSum: Reducer.sum,
946
+ * theAverage: Reducer.average
947
+ * });
948
+ *
949
+ * Stream.range({ amount: 9 }).reduce(red);
950
+ * // => { theSum: 36, theAverage: 4 }
951
+ * ```
952
+ */
953
+ export function combineObj<T, R extends { readonly [key: string]: unknown }>(
954
+ reducerObj: { readonly [K in keyof R]: Reducer<T, R[K]> } & Record<
955
+ string,
956
+ Reducer<T, unknown>
957
+ >
958
+ ): Reducer<T, R> {
959
+ const createState = (): Record<
960
+ keyof R,
961
+ {
962
+ reducer: Reducer<T, unknown>;
963
+ halted: boolean;
964
+ halt(): void;
965
+ state: unknown;
966
+ }
967
+ > => {
968
+ const allState: any = {};
969
+
970
+ for (const key in reducerObj) {
971
+ const reducer = reducerObj[key];
972
+
973
+ const result = {
974
+ reducer,
975
+ halted: false,
976
+ halt(): void {
977
+ result.halted = true;
978
+ },
979
+ state: OptLazy(reducer.init),
980
+ };
981
+
982
+ allState[key] = result;
983
+ }
984
+
985
+ return allState;
986
+ };
987
+
988
+ return create<T, R, ReturnType<typeof createState>>(
989
+ createState,
990
+ (allState, next, index, halt) => {
991
+ let anyNotHalted = false;
992
+
993
+ for (const key in allState) {
994
+ const red = allState[key];
995
+
996
+ if (red.halted) {
997
+ continue;
998
+ }
999
+
1000
+ red.state = red.reducer.next(red.state, next, index, red.halt);
1001
+
1002
+ if (!red.halted) {
1003
+ anyNotHalted = true;
1004
+ }
1005
+ }
1006
+
1007
+ if (!anyNotHalted) {
1008
+ halt();
1009
+ }
1010
+
1011
+ return allState;
1012
+ },
1013
+ (allState) => {
1014
+ const result: any = {};
1015
+
1016
+ for (const key in allState) {
1017
+ const st = allState[key];
1018
+ result[key] = st.reducer.stateToResult(st.state);
1019
+ }
1020
+
1021
+ return result;
1022
+ }
1023
+ );
1024
+ }
1025
+ }