atomic-fns 0.2.0 → 0.2.3
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/.esdoc.json +19 -1
- package/.eslintrc.json +1 -0
- package/README.md +8 -9
- package/package.json +9 -9
- package/src/collections/index.d.ts +658 -0
- package/src/collections/index.js +1068 -0
- package/src/collections/index.ts +1162 -0
- package/src/datetime/index.d.ts +6 -0
- package/src/datetime/index.js +6 -0
- package/src/datetime/index.ts +7 -0
- package/src/{functools.d.ts → functools/index.d.ts} +10 -5
- package/src/{functools.js → functools/index.js} +9 -4
- package/src/{functools.ts → functools/index.ts} +11 -5
- package/src/globals/index.d.ts +291 -0
- package/src/globals/index.js +391 -0
- package/src/globals/index.ts +456 -0
- package/src/index.d.ts +8 -7
- package/src/index.js +8 -7
- package/src/index.ts +8 -7
- package/src/itertools/index.d.ts +254 -0
- package/src/itertools/index.js +485 -0
- package/src/itertools/index.ts +511 -0
- package/src/math/index.d.ts +21 -0
- package/src/math/index.js +39 -0
- package/src/math/index.ts +44 -0
- package/src/{operators.d.ts → operators/index.d.ts} +8 -6
- package/src/{operators.js → operators/index.js} +69 -26
- package/src/{operators.ts → operators/index.ts} +77 -28
- package/src/random/index.d.ts +42 -0
- package/src/random/index.js +65 -0
- package/src/random/index.ts +66 -0
- package/tests/collections.spec.ts +119 -30
- package/tests/globals.spec.ts +91 -49
- package/tests/itertools.spec.ts +207 -14
- package/tests/operators.spec.ts +7 -29
- package/tests/random.spec.ts +46 -0
- package/tsconfig.json +19 -3
- package/src/collections.d.ts +0 -224
- package/src/collections.js +0 -570
- package/src/collections.ts +0 -637
- package/src/datetime.d.ts +0 -1
- package/src/datetime.js +0 -1
- package/src/datetime.ts +0 -1
- package/src/globals.d.ts +0 -175
- package/src/globals.js +0 -329
- package/src/globals.ts +0 -373
- package/src/itertools.d.ts +0 -16
- package/src/itertools.js +0 -101
- package/src/itertools.ts +0 -112
- package/src/math.d.ts +0 -16
- package/src/math.js +0 -67
- package/src/math.ts +0 -73
- package/src/objects.d.ts +0 -0
- package/src/objects.js +0 -1
- package/src/objects.ts +0 -0
- package/src/random.d.ts +0 -22
- package/src/random.js +0 -48
- package/src/random.ts +0 -49
- package/src/strings.d.ts +0 -0
- package/src/strings.js +0 -1
- package/src/strings.ts +0 -0
- package/src/util.d.ts +0 -0
- package/src/util.js +0 -1
- package/src/util.ts +0 -0
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module includes classes and functions to work with Container types.
|
|
3
|
+
*
|
|
4
|
+
* @module Collections
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { partial } from '../functools/index.js'
|
|
8
|
+
import {
|
|
9
|
+
call,
|
|
10
|
+
get,
|
|
11
|
+
isArray,
|
|
12
|
+
isArrayLike,
|
|
13
|
+
isBool,
|
|
14
|
+
isEmpty,
|
|
15
|
+
isFunc,
|
|
16
|
+
isIterable,
|
|
17
|
+
isNull,
|
|
18
|
+
isNumber,
|
|
19
|
+
isObject,
|
|
20
|
+
isString,
|
|
21
|
+
isSymbol,
|
|
22
|
+
Iteratee,
|
|
23
|
+
keys,
|
|
24
|
+
NotImplementedError,
|
|
25
|
+
set
|
|
26
|
+
} from '../globals/index.js'
|
|
27
|
+
import { enumerate } from '../itertools/index.js'
|
|
28
|
+
import { comp, eq, id } from '../operators/index.js'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A container type that provides contains().
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
* @interface Container
|
|
35
|
+
*/
|
|
36
|
+
export interface Container {
|
|
37
|
+
contains: (x) => boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* These are the "rich comparison" methods, inspired by Python operators.
|
|
42
|
+
*
|
|
43
|
+
*
|
|
44
|
+
* @interface Comparable
|
|
45
|
+
*/
|
|
46
|
+
export interface Comparable {
|
|
47
|
+
/** x < y: calls this.lt(other) reflection. */
|
|
48
|
+
lt?: (other) => boolean
|
|
49
|
+
/** x ≤ y: calls this.le(other) reflection. */
|
|
50
|
+
le?: (other) => boolean
|
|
51
|
+
/** x == y: calls this.eq(other) reflection. */
|
|
52
|
+
eq: (other) => boolean
|
|
53
|
+
/** x != y: calls this.ne(other) reflection. */
|
|
54
|
+
ne: (other) => boolean
|
|
55
|
+
/** x > y: calls this.gt(other) reflection. */
|
|
56
|
+
gt?: (other) => boolean
|
|
57
|
+
/** x ≥ y: calls this.ge(other) reflection. */
|
|
58
|
+
ge?: (other) => boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* A `Collection` is an iterable {@link Container} type.
|
|
63
|
+
* This is an abstract base class for user-defined collection types.
|
|
64
|
+
*
|
|
65
|
+
*
|
|
66
|
+
* @abstract
|
|
67
|
+
* @class Collection
|
|
68
|
+
* @implements {Container}
|
|
69
|
+
*/
|
|
70
|
+
export abstract class Collection implements Container {
|
|
71
|
+
/**
|
|
72
|
+
* Returns an iterator for the container items
|
|
73
|
+
* @return {Iterator}
|
|
74
|
+
*/
|
|
75
|
+
[Symbol.iterator]() {
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Implements the iterator protocol and returns the next item in the iterable.
|
|
81
|
+
*/
|
|
82
|
+
next() {}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Adds a new item to the container.
|
|
86
|
+
* @param item - The item to add to the collection.
|
|
87
|
+
*/
|
|
88
|
+
add(item) {}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Checks if item is present in the container.
|
|
92
|
+
* @param item - The item to search for in the collection.
|
|
93
|
+
* @returns `true` when the element is found, else `false`
|
|
94
|
+
*/
|
|
95
|
+
contains(item) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Returns the total number of elements in the container.
|
|
101
|
+
*/
|
|
102
|
+
size(): number {
|
|
103
|
+
throw new NotImplementedError()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Remove the first item from the container where `item == x`
|
|
108
|
+
* @returns `true` when the element is found and removed, else `false`
|
|
109
|
+
*/
|
|
110
|
+
remove(x) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Retrieve and remove the item at index `i`.
|
|
116
|
+
* @returns the item or undefined if not found.
|
|
117
|
+
*/
|
|
118
|
+
pop(i) {}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Remove all items from the container
|
|
122
|
+
*/
|
|
123
|
+
clear() {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Implements an iterable that allows backward iteration.
|
|
128
|
+
* @interface Reversible
|
|
129
|
+
*/
|
|
130
|
+
export interface Reversible<T = unknown> {
|
|
131
|
+
reversed: () => Iterator<T>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A sequence is an iterable {@link Collection} type with efficient index-based access.
|
|
136
|
+
* @abstract
|
|
137
|
+
* @class Sequence
|
|
138
|
+
* @extends {Collection}
|
|
139
|
+
* @template T
|
|
140
|
+
*/
|
|
141
|
+
export abstract class Sequence<T> extends Collection implements Reversible<T> {
|
|
142
|
+
/**
|
|
143
|
+
* Return the item at the given key or index
|
|
144
|
+
* @param {*} key
|
|
145
|
+
* @memberof Sequence
|
|
146
|
+
*/
|
|
147
|
+
get(key): T | undefined {
|
|
148
|
+
throw new NotImplementedError()
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Set a new value at the given key or index
|
|
153
|
+
*
|
|
154
|
+
* @param {*} key
|
|
155
|
+
* @param {T} val
|
|
156
|
+
* @memberof Sequence
|
|
157
|
+
*/
|
|
158
|
+
set(key, val: T) {
|
|
159
|
+
throw new NotImplementedError()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Deletes the given key or index, and its value.
|
|
164
|
+
* Raises ValueError if not key is found.
|
|
165
|
+
*
|
|
166
|
+
* @param {*} key
|
|
167
|
+
* @memberof Sequence
|
|
168
|
+
*/
|
|
169
|
+
delete(key) {
|
|
170
|
+
throw new NotImplementedError()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Adds a new item to the end of the sequence.
|
|
175
|
+
*
|
|
176
|
+
* @param {T} x
|
|
177
|
+
* @memberof Sequence
|
|
178
|
+
*/
|
|
179
|
+
append(x: T) {}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Append all the items to the sequence.
|
|
183
|
+
*
|
|
184
|
+
* @param {Iterable<T>} iter
|
|
185
|
+
* @memberof Sequence
|
|
186
|
+
*/
|
|
187
|
+
extend(iter: Iterable<T>) {}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Return the index of x in the sequence, or undefined if not found.
|
|
191
|
+
*
|
|
192
|
+
* @param {T} item
|
|
193
|
+
* @return {(number | undefined)}
|
|
194
|
+
* @memberof Sequence
|
|
195
|
+
*/
|
|
196
|
+
indexOf(item: T): number | undefined {
|
|
197
|
+
throw new NotImplementedError()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
size() {
|
|
201
|
+
return 0
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
reversed(): Iterator<T> {
|
|
205
|
+
throw new NotImplementedError()
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Returns a new immutable Set object with elements from `iterable`. Its contents cannot be altered after it's created.
|
|
211
|
+
*
|
|
212
|
+
*
|
|
213
|
+
* @class FrozenSet
|
|
214
|
+
* @extends {Set<T>}
|
|
215
|
+
* @template T
|
|
216
|
+
*/
|
|
217
|
+
export class FrozenSet<T = any> extends Set<T> {
|
|
218
|
+
constructor(iterable: Iterable<T>) {
|
|
219
|
+
super(iterable)
|
|
220
|
+
return this.freeze()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @private */
|
|
224
|
+
add() {
|
|
225
|
+
throw TypeError('FrozenSet cannot be modified.')
|
|
226
|
+
return this // eslint-disable-line
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** @private */
|
|
230
|
+
delete() {
|
|
231
|
+
throw TypeError('FrozenSet cannot be modified.')
|
|
232
|
+
return false // eslint-disable-line
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** @private */
|
|
236
|
+
clear() {
|
|
237
|
+
throw TypeError('FrozenSet cannot be modified.')
|
|
238
|
+
return this // eslint-disable-line
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** @private */
|
|
242
|
+
freeze() {
|
|
243
|
+
return Object.freeze(this)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Creates a new array with all `falsey` and empty values removed.
|
|
249
|
+
* @param arr The array to compact
|
|
250
|
+
* @returns A new array with the filtered values.
|
|
251
|
+
* @example
|
|
252
|
+
```js
|
|
253
|
+
compact([0, 1, false, 2, '', 3])
|
|
254
|
+
// => [1, 2, 3]
|
|
255
|
+
```
|
|
256
|
+
*/
|
|
257
|
+
export function compact(arr: any[])
|
|
258
|
+
export function compact(arr: Object)
|
|
259
|
+
export function compact(arr) {
|
|
260
|
+
if (arr == null) return
|
|
261
|
+
if (Array.isArray(arr)) return arr.filter((x) => !isEmpty(x))
|
|
262
|
+
const result = {}
|
|
263
|
+
for (const key of Object.keys(arr)) {
|
|
264
|
+
if (!isEmpty(arr[key])) {
|
|
265
|
+
result[key] = arr[key]
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return result
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function count<T>(iterable: Iterable<T> | Object, func: Iteratee<T>) {
|
|
272
|
+
const counters = {}
|
|
273
|
+
forEach(iterable, (value: T, key) => {
|
|
274
|
+
const result = func(value, key)
|
|
275
|
+
counters[result] = get(result, counters, 0) + 1
|
|
276
|
+
})
|
|
277
|
+
return counters
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Creates a function that can be used to create named tuple-like objects.
|
|
282
|
+
* @example
|
|
283
|
+
```js
|
|
284
|
+
* let Point = namedtuple('x', 'y', 'z')
|
|
285
|
+
* let userObj = User(0, 0, 0)
|
|
286
|
+
* // => {x: 0, y: 0, z: 0}
|
|
287
|
+
```
|
|
288
|
+
* @param fields - A list of field names
|
|
289
|
+
* @returns A function that can be called with the field values
|
|
290
|
+
*/
|
|
291
|
+
export function namedtuple(...fields: string[]) {
|
|
292
|
+
return (...args) => fields.reduce((prev, f, i) => set(f, prev, args[i], false, true), {})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Iterates over elements of collection, returning an array of all elements where predicate returns truthy value.
|
|
297
|
+
*
|
|
298
|
+
* The predicate is invoked with three arguments: `(value, index|key, arr)`.
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
```js
|
|
302
|
+
* let users = [
|
|
303
|
+
{ 'user': 'barney', 'age': 36, 'active': true },
|
|
304
|
+
{ 'user': 'fred', 'age': 40, 'active': false }
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
filter(users, o => !o.active)
|
|
308
|
+
// => objects for ['fred']
|
|
309
|
+
|
|
310
|
+
// The shape iteratee shorthand.
|
|
311
|
+
filter(users, { age: 36, active: true })
|
|
312
|
+
// => objects for ['barney']
|
|
313
|
+
|
|
314
|
+
// The property iteratee shorthand.
|
|
315
|
+
filter(users, 'active')
|
|
316
|
+
// => objects for ['barney']
|
|
317
|
+
```
|
|
318
|
+
* @param {Array} arr The collection to iterate over
|
|
319
|
+
* @param {Function} fn The predicate function invoked for each item
|
|
320
|
+
* @returns {Array} The new filtered array
|
|
321
|
+
*/
|
|
322
|
+
export function filter(arr, fn: Iteratee | PropertyKey | Object = isNull) {
|
|
323
|
+
if (Array.isArray(arr)) {
|
|
324
|
+
if (isFunc(fn)) return arr.filter(fn)
|
|
325
|
+
if (isNumber(fn) || isString(fn) || isSymbol(fn)) return arr.filter((x) => get(fn, x))
|
|
326
|
+
if (isObject(fn)) return arr.filter(matches(fn))
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Iterates over elements of collection, returning the first element where predicate returns truthy value. The predicate is invoked with three arguments: `(value, index|key, collection)`.
|
|
332
|
+
* @example
|
|
333
|
+
```js
|
|
334
|
+
let users = [
|
|
335
|
+
{ 'user': 'barney', 'age': 36, 'active': true },
|
|
336
|
+
{ 'user': 'fred', 'age': 40, 'active': false },
|
|
337
|
+
{ 'user': 'pebbles', 'age': 1, 'active': true }
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
find(users, (o) => o.age < 40)
|
|
341
|
+
// => object for 'barney'
|
|
342
|
+
|
|
343
|
+
// The shape iteratee shorthand.
|
|
344
|
+
find(users, { 'age': 1, 'active': true })
|
|
345
|
+
// => object for 'pebbles'
|
|
346
|
+
|
|
347
|
+
// The `property` iteratee shorthand.
|
|
348
|
+
find(users, 'active')
|
|
349
|
+
// => object for 'barney'
|
|
350
|
+
```
|
|
351
|
+
* @param {Array} arr The collection to iterate over.
|
|
352
|
+
* @param {Function} fn The function invoked per iteration.
|
|
353
|
+
* @returns {*} The matched element, else `undefined`.
|
|
354
|
+
* @see {@link findRight}
|
|
355
|
+
*/
|
|
356
|
+
export function find(arr, fn: Iteratee | PropertyKey | Object) {
|
|
357
|
+
if (Array.isArray(arr)) {
|
|
358
|
+
if (isFunc(fn)) return arr.find(fn)
|
|
359
|
+
if (isNumber(fn) || isString(fn) || isSymbol(fn)) return arr.find((x) => x?.[fn])
|
|
360
|
+
if (isObject(fn)) return arr.find(matches(fn))
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* This method is like {@link find} except that it iterates from right to left.
|
|
365
|
+
* @example
|
|
366
|
+
```js
|
|
367
|
+
findRight([1, 2, 3, 4], (n) => n % 2 === 1)
|
|
368
|
+
// => 3
|
|
369
|
+
```
|
|
370
|
+
* @param {Array} arr The collection to iterate over.
|
|
371
|
+
* @param {Function} fn The function invoked per iteration.
|
|
372
|
+
* @returns {*} The matched element, else `undefined`.
|
|
373
|
+
* @see {@link find}
|
|
374
|
+
*/
|
|
375
|
+
export function findRight(arr, fn: Iteratee | PropertyKey | Object) {
|
|
376
|
+
if (Array.isArray(arr)) {
|
|
377
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
378
|
+
const x = arr[i]
|
|
379
|
+
if (typeof fn === 'function') {
|
|
380
|
+
if (fn(x)) return x
|
|
381
|
+
} else if (typeof fn === 'string') {
|
|
382
|
+
if (x?.[fn]) return x
|
|
383
|
+
} else if (isObject(fn)) {
|
|
384
|
+
if (matches(fn)(x)) return x
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Creates a function that performs a partial deep comparison between a given object and `shape`, returning `true` if the given object has equivalent property values, else `false`.
|
|
392
|
+
* @example
|
|
393
|
+
```js
|
|
394
|
+
let objects = [
|
|
395
|
+
{ a: 1, b: 2, c: 3 },
|
|
396
|
+
{ a: 4, b: 5, c: 6 }
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
filter(objects, matches({ a: 4, c: 6 }))
|
|
400
|
+
// => [{ a: 4, b: 5, c: 6 }]
|
|
401
|
+
```
|
|
402
|
+
* @param shape
|
|
403
|
+
* @returns
|
|
404
|
+
*/
|
|
405
|
+
export const matches = (shape) => (obj) => {
|
|
406
|
+
if (!shape || !obj) return false
|
|
407
|
+
for (const key in shape) {
|
|
408
|
+
if (obj[key] !== shape[key]) return false
|
|
409
|
+
}
|
|
410
|
+
return true
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Iterates over elements of collection and invokes `iteratee` for each element. The iteratee is invoked with three arguments: `(value, index|key, collection)`. Iteratee functions may exit iteration early by explicitly returning `false`.
|
|
415
|
+
* @example
|
|
416
|
+
```js
|
|
417
|
+
forEach([1, 2], (value) => {
|
|
418
|
+
console.log(value)
|
|
419
|
+
})
|
|
420
|
+
// => Logs `1` then `2`.
|
|
421
|
+
|
|
422
|
+
forEach({ 'a': 1, 'b': 2 }, (value, key) => {
|
|
423
|
+
console.log(key)
|
|
424
|
+
})
|
|
425
|
+
// => Logs 'a' then 'b' (iteration order is not guaranteed).
|
|
426
|
+
```
|
|
427
|
+
* @param {Array|Object} collection The collection to iterate over.
|
|
428
|
+
* @param {Iteratee} fn The function invoked per iteration.
|
|
429
|
+
* @returns {*} Returns `collection`.
|
|
430
|
+
* @see {@link forEachRight}
|
|
431
|
+
* @see {@link filter}
|
|
432
|
+
* @see {@link map}
|
|
433
|
+
*/
|
|
434
|
+
export function forEach<T>(collection: T[], fn: Iteratee<T>)
|
|
435
|
+
export function forEach<T>(collection: Iterable<T>, fn: Iteratee<T>)
|
|
436
|
+
export function forEach<T>(collection: Object, fn: Iteratee<T>)
|
|
437
|
+
export function forEach<T>(collection, fn: Iteratee<T>) {
|
|
438
|
+
if (Array.isArray(collection)) {
|
|
439
|
+
for (let i = 0; i < collection.length; i++) {
|
|
440
|
+
const res = fn(collection[i], i, collection)
|
|
441
|
+
if (res === false) return collection
|
|
442
|
+
}
|
|
443
|
+
} else if (isIterable(collection)) {
|
|
444
|
+
for (const [index, value] of enumerate(collection)) {
|
|
445
|
+
const res = fn(value as T, index, collection)
|
|
446
|
+
if (res === false) return collection
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
for (const key of keys(collection)) {
|
|
450
|
+
const res = fn(collection[key], key, collection)
|
|
451
|
+
if (res === false) return collection
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* This method is like {@link forEach} except that it iterates over the collection from right to left.
|
|
458
|
+
|
|
459
|
+
* @example
|
|
460
|
+
```js
|
|
461
|
+
forEachRight([1, 2], (value) => {
|
|
462
|
+
console.log(value)
|
|
463
|
+
})
|
|
464
|
+
// => Logs `2` then `1`.
|
|
465
|
+
```
|
|
466
|
+
* @param {Array|Object} collection The collection to iterate over.
|
|
467
|
+
* @param {Iteratee} fn The function invoked per iteration.
|
|
468
|
+
* @returns {*} Returns `collection`.
|
|
469
|
+
* @see {@link forEach}
|
|
470
|
+
* @see {@link filter}
|
|
471
|
+
* @see {@link map}
|
|
472
|
+
*/
|
|
473
|
+
export function forEachRight(collection: any[] | Object, fn: Iteratee) {
|
|
474
|
+
if (Array.isArray(collection)) {
|
|
475
|
+
for (let i = collection.length - 1; i >= 0; i--) {
|
|
476
|
+
const value = collection[i]
|
|
477
|
+
const res = fn(value, i, collection)
|
|
478
|
+
if (res === false) return collection
|
|
479
|
+
}
|
|
480
|
+
} else if (isObject(collection)) {
|
|
481
|
+
const keys = Object.keys(collection)
|
|
482
|
+
for (let i = keys.length - 1; i >= 0; i--) {
|
|
483
|
+
const key = keys[i]
|
|
484
|
+
const value = collection[key]
|
|
485
|
+
const res = fn(value, i, collection)
|
|
486
|
+
if (res === false) return collection
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return collection
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Flattens an array or object. Arrays will be flattened recursively up to `depth` times. Objects will be flattened recursively.
|
|
494
|
+
* @example
|
|
495
|
+
```js
|
|
496
|
+
flatten([1, [2, [3, [4]], 5]])
|
|
497
|
+
// => [1, 2, [3, [4]], 5]
|
|
498
|
+
|
|
499
|
+
flatten({
|
|
500
|
+
dates: {
|
|
501
|
+
expiry_date: '30 sep 2018',
|
|
502
|
+
available: '30 sep 2017',
|
|
503
|
+
min_contract_period: [
|
|
504
|
+
{
|
|
505
|
+
id: 1,
|
|
506
|
+
name: '1 month'
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
},
|
|
510
|
+
price: {
|
|
511
|
+
currency: 'RM',
|
|
512
|
+
value: 1500
|
|
513
|
+
}
|
|
514
|
+
})
|
|
515
|
+
// => {
|
|
516
|
+
'dates.expiry_date': '30 sep 2018',
|
|
517
|
+
'dates.available': '30 sep 2017',
|
|
518
|
+
'dates.min_contract_period[0].id': 1,
|
|
519
|
+
'dates.min_contract_period[0].name': '1 month',
|
|
520
|
+
'price.currency': 'RM',
|
|
521
|
+
'price.value': 1500
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
* @param {Array|Object} arr The array or object to flatten.
|
|
525
|
+
* @param {number} [depth=1] The max recursion depth.
|
|
526
|
+
* @returns {*} The new flattened array or object.
|
|
527
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat Array.flat()}
|
|
528
|
+
*/
|
|
529
|
+
export function flatten(arr: any[] | Object, depth: boolean | number = 1) {
|
|
530
|
+
if (!depth) return arr
|
|
531
|
+
if (Array.isArray(arr)) return flattenArray(arr, depth, [])
|
|
532
|
+
if (isObject(arr)) return flattenObj(arr)
|
|
533
|
+
return arr
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function flattenArray(arr: any[], depth: boolean | number = 1, result: any[] = []) {
|
|
537
|
+
for (const value of arr) {
|
|
538
|
+
if (depth && Array.isArray(value)) {
|
|
539
|
+
flattenArray(value, typeof depth === 'number' ? depth - 1 : depth, result)
|
|
540
|
+
} else {
|
|
541
|
+
result.push(value)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return result
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function flattenObj(obj, prefix = '', result = {}, keepNull = false) {
|
|
548
|
+
if (isString(obj) || isNumber(obj) || isBool(obj) || (keepNull && isNull(obj))) {
|
|
549
|
+
result[prefix] = obj
|
|
550
|
+
return result
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (isArray(obj) || isObject(obj)) {
|
|
554
|
+
for (const i of Object.keys(obj)) {
|
|
555
|
+
let pref = prefix
|
|
556
|
+
if (isArray(obj)) {
|
|
557
|
+
pref = `${pref}[${i}]`
|
|
558
|
+
} else {
|
|
559
|
+
if (prefix) {
|
|
560
|
+
pref = `${prefix}.${i}`
|
|
561
|
+
} else {
|
|
562
|
+
pref = i
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
flattenObj(obj[i], pref, result, keepNull)
|
|
566
|
+
}
|
|
567
|
+
return result
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return result
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Creates an array of values by running each element in collection thru iteratee. The iteratee is invoked with three arguments: `(value, index|key, collection)`.
|
|
575
|
+
* @example
|
|
576
|
+
```js
|
|
577
|
+
function square(n) {
|
|
578
|
+
return n * n
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
map([4, 8], square)
|
|
582
|
+
// => [16, 64]
|
|
583
|
+
|
|
584
|
+
map({ a: 4, b: 8 }, square)
|
|
585
|
+
// => [16, 64] (iteration order is not guaranteed)
|
|
586
|
+
|
|
587
|
+
let users = [
|
|
588
|
+
{ user: 'barney' },
|
|
589
|
+
{ user: 'fred' }
|
|
590
|
+
]
|
|
591
|
+
|
|
592
|
+
// The `property` iteratee shorthand.
|
|
593
|
+
map(users, 'user')
|
|
594
|
+
// => ['barney', 'fred']
|
|
595
|
+
```
|
|
596
|
+
* @param {Array|Object} arr The collection to iterate over.
|
|
597
|
+
* @param {Iteratee|PropertyKey} fn The function invoked per iteration.
|
|
598
|
+
* @returns Returns the new mapped array.
|
|
599
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map Array.map()}
|
|
600
|
+
*/
|
|
601
|
+
export function map(arr, fn: Iteratee | PropertyKey) {
|
|
602
|
+
if (Array.isArray(arr)) {
|
|
603
|
+
return arr.map((x, i) => {
|
|
604
|
+
if (typeof fn === 'function') return fn(x, i, arr)
|
|
605
|
+
return x?.[fn]
|
|
606
|
+
})
|
|
607
|
+
}
|
|
608
|
+
if (isObject(arr)) {
|
|
609
|
+
return Object.keys(arr).map((key) => {
|
|
610
|
+
if (typeof fn === 'function') return fn(arr[key], key, arr)
|
|
611
|
+
return get(fn, arr[key])
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Creates an object composed of the picked object properties.
|
|
618
|
+
* @param {Object} obj The source object.
|
|
619
|
+
* @param {Iteratee|PropertyKey[]} paths The properties to pick. If `paths` is a function, it will be invoked per property with two values `(value, key)`.
|
|
620
|
+
* @returns A new object with the properties.
|
|
621
|
+
* @example
|
|
622
|
+
```js
|
|
623
|
+
let object = { a: 1, b: '2', c: 3 }
|
|
624
|
+
|
|
625
|
+
pick(object, ['a', 'c'])
|
|
626
|
+
// => { a: 1, c: 3 }
|
|
627
|
+
|
|
628
|
+
pick(object, (x) => isNumber(x))
|
|
629
|
+
// => { a: 1, c: 3 }
|
|
630
|
+
```
|
|
631
|
+
*
|
|
632
|
+
* @see {@link omit}
|
|
633
|
+
*/
|
|
634
|
+
export function pick(obj: Object, paths: Iteratee | PropertyKey[]) {
|
|
635
|
+
if (!obj) return {}
|
|
636
|
+
|
|
637
|
+
const result = {}
|
|
638
|
+
|
|
639
|
+
if (typeof paths === 'function') {
|
|
640
|
+
for (const key of Object.keys(obj)) {
|
|
641
|
+
if (paths(obj[key], key)) {
|
|
642
|
+
result[key] = obj[key]
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return result
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
for (const key of paths) {
|
|
649
|
+
result[key] = get(key, obj)
|
|
650
|
+
}
|
|
651
|
+
return result
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* The opposite of {@link pick} - this method creates an object composed of the own and inherited enumerable property paths of `object` that are not omitted.
|
|
656
|
+
* @example
|
|
657
|
+
```js
|
|
658
|
+
let object = { 'a': 1, 'b': '2', 'c': 3 }
|
|
659
|
+
|
|
660
|
+
omit(object, ['a', 'c'])
|
|
661
|
+
// => { 'b': '2' }
|
|
662
|
+
|
|
663
|
+
omit(object, (x) => isNumber(x))
|
|
664
|
+
// => { 'b': '2' }
|
|
665
|
+
```
|
|
666
|
+
* @param {Object} obj The source object.
|
|
667
|
+
* @param {Iteratee|PropertyKey[]} paths The property paths to omit.
|
|
668
|
+
* @returns {Object} Returns the new object.
|
|
669
|
+
* @see {@link pick}
|
|
670
|
+
*/
|
|
671
|
+
export function omit(obj: Object, paths: Iteratee | PropertyKey[]) {
|
|
672
|
+
if (!obj) return {}
|
|
673
|
+
|
|
674
|
+
const result = {}
|
|
675
|
+
|
|
676
|
+
if (typeof paths === 'function') {
|
|
677
|
+
for (const key of Object.keys(obj)) {
|
|
678
|
+
if (!paths(obj[key], key)) {
|
|
679
|
+
result[key] = obj[key]
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return result
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
Object.assign(result, obj)
|
|
686
|
+
for (const key of paths) {
|
|
687
|
+
delete result[key]
|
|
688
|
+
}
|
|
689
|
+
return result
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* This method is like {@link find} except that it returns the index of the first element `predicate` returns truthy for instead of the element itself.
|
|
694
|
+
* @param {Array} obj The array to inspect.
|
|
695
|
+
* @param {string|Object|Iteratee} fn The predicate function
|
|
696
|
+
* @param {number} [start=0] The index to search from.
|
|
697
|
+
* @returns {number} The index of the found value, else -1
|
|
698
|
+
* @example
|
|
699
|
+
```js
|
|
700
|
+
let users = [
|
|
701
|
+
{ 'user': 'barney', 'active': false },
|
|
702
|
+
{ 'user': 'fred', 'active': false },
|
|
703
|
+
{ 'user': 'pebbles', 'active': true }
|
|
704
|
+
]
|
|
705
|
+
|
|
706
|
+
index(users, (o) => o.user == 'barney')
|
|
707
|
+
// => 0
|
|
708
|
+
|
|
709
|
+
// The `shape` iteratee shorthand.
|
|
710
|
+
index(users, { 'user': 'fred', 'active': false })
|
|
711
|
+
// => 1
|
|
712
|
+
|
|
713
|
+
// The `property` iteratee shorthand.
|
|
714
|
+
index(users, 'active')
|
|
715
|
+
// => 2
|
|
716
|
+
```
|
|
717
|
+
* @see {@link indexOf}
|
|
718
|
+
* @see {@link lastIndex}
|
|
719
|
+
*
|
|
720
|
+
*/
|
|
721
|
+
export function index(obj, fn: Iteratee | string | Object, start = 0) {
|
|
722
|
+
if (obj == null) return
|
|
723
|
+
const length = obj.length
|
|
724
|
+
if (length && start < 0) {
|
|
725
|
+
start = Math.max(start + length, 0)
|
|
726
|
+
}
|
|
727
|
+
if (!length || start >= length) return -1
|
|
728
|
+
if (isObject(fn)) {
|
|
729
|
+
fn = matches(fn)
|
|
730
|
+
}
|
|
731
|
+
for (; start < length; start++) {
|
|
732
|
+
if (typeof fn === 'function') {
|
|
733
|
+
if (fn(obj[start], start, obj)) return start
|
|
734
|
+
} else {
|
|
735
|
+
if (obj[start]?.[fn as PropertyKey]) return start
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
return -1
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* This method is like {@link index} except that it searches for a given value directly, instead of using a predicate function.
|
|
743
|
+
* @param {Array} obj The array to inspect.
|
|
744
|
+
* @param {*} value The value to find
|
|
745
|
+
* @param {number} [start=0] The index to search from.
|
|
746
|
+
* @returns {number} The index of the found value, else -1
|
|
747
|
+
* @example
|
|
748
|
+
```js
|
|
749
|
+
indexOf([1, 2, 1, 2], 2)
|
|
750
|
+
// => 1
|
|
751
|
+
|
|
752
|
+
// Search from a `start` index.
|
|
753
|
+
indexOf([1, 2, 1, 2], 2, 2)
|
|
754
|
+
// => 3
|
|
755
|
+
```
|
|
756
|
+
* @see {@link index}
|
|
757
|
+
* @see {@link lastIndexOf}
|
|
758
|
+
*/
|
|
759
|
+
export function indexOf(obj, value, start = 0) {
|
|
760
|
+
if (obj == null) return
|
|
761
|
+
const op = call(obj, 'indexOf', value, start)
|
|
762
|
+
if (op != null) return op
|
|
763
|
+
const length = obj.length
|
|
764
|
+
if (length && start < 0) {
|
|
765
|
+
start = Math.max(start + length, 0)
|
|
766
|
+
}
|
|
767
|
+
if (!length || start >= length) return -1
|
|
768
|
+
for (; start < length; start++) {
|
|
769
|
+
if (eq(value, obj[start])) return start
|
|
770
|
+
}
|
|
771
|
+
return -1
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* This method is like {@link index} except that it iterates the collection from right to left.
|
|
776
|
+
* @param {Array} obj The array to inspect.
|
|
777
|
+
* @param {string|Iteratee|Object} fn The function invoked per iteration.
|
|
778
|
+
* @param {number} [start] The index to search from.
|
|
779
|
+
* @returns {number} The index of the found value, else -1
|
|
780
|
+
* @example
|
|
781
|
+
```js
|
|
782
|
+
let users = [
|
|
783
|
+
{ 'user': 'barney', 'active': true },
|
|
784
|
+
{ 'user': 'fred', 'active': false },
|
|
785
|
+
{ 'user': 'pebbles', 'active': false }
|
|
786
|
+
]
|
|
787
|
+
|
|
788
|
+
findLastIndex(users, (o) => o.user == 'pebbles')
|
|
789
|
+
// => 2
|
|
790
|
+
|
|
791
|
+
// The `shape` iteratee shorthand.
|
|
792
|
+
findLastIndex(users, { 'user': 'barney', 'active': true })
|
|
793
|
+
// => 0
|
|
794
|
+
|
|
795
|
+
// The `property` iteratee shorthand.
|
|
796
|
+
findLastIndex(users, 'active')
|
|
797
|
+
// => 0
|
|
798
|
+
```
|
|
799
|
+
* @see {@link index}
|
|
800
|
+
* @see {@link lastIndexOf}
|
|
801
|
+
*/
|
|
802
|
+
export function lastIndex(obj: any[], fn: string | Iteratee | Object, start?) {
|
|
803
|
+
if (obj == null) return
|
|
804
|
+
const length = obj.length
|
|
805
|
+
if (start == null) start = length - 1
|
|
806
|
+
if (!length || start < 0) return -1
|
|
807
|
+
if (isObject(fn)) {
|
|
808
|
+
fn = matches(fn)
|
|
809
|
+
}
|
|
810
|
+
for (; start >= 0; start--) {
|
|
811
|
+
if (typeof fn === 'function') {
|
|
812
|
+
if (fn(obj[start])) return start
|
|
813
|
+
} else {
|
|
814
|
+
if (obj[start]?.[fn as PropertyKey]) return start
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return -1
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* This method is like {@link indexOf} except that it iterates the collection from right to left.
|
|
821
|
+
* @param {Array} obj The array to inspect.
|
|
822
|
+
* @param {*} value The value to find
|
|
823
|
+
* @param {number} [start] The index to search from.
|
|
824
|
+
* @returns {number} The index of the found value, else -1
|
|
825
|
+
* @example
|
|
826
|
+
```js
|
|
827
|
+
lastIndexOf([1, 2, 1, 2], 2)
|
|
828
|
+
// => 3
|
|
829
|
+
|
|
830
|
+
// Search from the `fromIndex`.
|
|
831
|
+
lastIndexOf([1, 2, 1, 2], 2, 2)
|
|
832
|
+
// => 1
|
|
833
|
+
```
|
|
834
|
+
* @see {@link lastIndex}
|
|
835
|
+
* @see {@link indexOf}
|
|
836
|
+
*/
|
|
837
|
+
export function lastIndexOf(obj: any[], value, start?) {
|
|
838
|
+
if (obj == null) return
|
|
839
|
+
const op = call(obj, 'lastIndexOf', value, start)
|
|
840
|
+
if (op != null) return op
|
|
841
|
+
const length = obj.length
|
|
842
|
+
if (start == null) start = length - 1
|
|
843
|
+
if (!length || start < 0) return -1
|
|
844
|
+
for (; start >= 0; start--) {
|
|
845
|
+
if (eq(value, obj[start])) return start
|
|
846
|
+
}
|
|
847
|
+
return -1
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Creates a shallow clone of `value`. If `deep` is `true` it will clone it recursively.
|
|
852
|
+
* @param {*} value
|
|
853
|
+
* @param {boolean} [deep=false]
|
|
854
|
+
* @return The clone value
|
|
855
|
+
* @see {@link cloneArray}
|
|
856
|
+
* @see {@link cloneTypedArray}
|
|
857
|
+
*/
|
|
858
|
+
export function clone(value, deep = false) {
|
|
859
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
860
|
+
return cloneTypedArray(value, deep)
|
|
861
|
+
}
|
|
862
|
+
if (isArray(value) || isArrayLike(value)) {
|
|
863
|
+
return cloneArray(value, deep)
|
|
864
|
+
}
|
|
865
|
+
if (isObject(value)) {
|
|
866
|
+
if (isFunc(value.clone)) {
|
|
867
|
+
return value.clone()
|
|
868
|
+
}
|
|
869
|
+
const copy = {}
|
|
870
|
+
for (const key in value) {
|
|
871
|
+
if (deep) {
|
|
872
|
+
copy[key] = clone(value[key], deep)
|
|
873
|
+
} else {
|
|
874
|
+
copy[key] = value[key]
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return copy
|
|
878
|
+
}
|
|
879
|
+
return value
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Clones an array. If `deep` is `false` (default) the clone will be shallow. Otherwise {@link https://developer.mozilla.org/en-US/docs/Web/API/structuredClone structuredClone} is used.
|
|
884
|
+
* @param arr The array to clone
|
|
885
|
+
* @param [deep=false] Creates a deep clone using `structuredClone` if true.
|
|
886
|
+
* @returns The new array
|
|
887
|
+
* @see {@link clone}
|
|
888
|
+
*/
|
|
889
|
+
export function cloneArray(arr, deep = false) {
|
|
890
|
+
if (!deep) return Array.from(arr)
|
|
891
|
+
return structuredClone(arr)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Clones a typed array. If `deep` is `false` (default) the clone will be shallow. Otherwise {@link https://developer.mozilla.org/en-US/docs/Web/API/structuredClone structuredClone} is used.
|
|
896
|
+
* @param arr The array to clone
|
|
897
|
+
* @param [deep=false] Creates a deep clone using `structuredClone` if true.
|
|
898
|
+
* @returns The new array
|
|
899
|
+
* @see {@link clone}
|
|
900
|
+
*/
|
|
901
|
+
export function cloneTypedArray(typedArray, isDeep) {
|
|
902
|
+
const buffer = isDeep ? structuredClone(typedArray) : typedArray.buffer
|
|
903
|
+
return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Creates a duplicate-free version of an array, using a `Set` for equality comparisons, in which only the first occurrence of each element is kept. The order of result values is not guaranteed.
|
|
908
|
+
* @param arr The array containing duplicated elements
|
|
909
|
+
* @param fn The iteratee invoked per element.
|
|
910
|
+
* @returns Returns the new duplicate free array.
|
|
911
|
+
* @example
|
|
912
|
+
```js
|
|
913
|
+
uniq([2, 1, 2])
|
|
914
|
+
// => [2, 1]
|
|
915
|
+
|
|
916
|
+
uniq([2.1, 1.2, 2.3], Math.floor)
|
|
917
|
+
// => [2.1, 1.2]
|
|
918
|
+
```
|
|
919
|
+
*
|
|
920
|
+
* @see {@link sortedUniq}
|
|
921
|
+
*/
|
|
922
|
+
export function uniq<T = any>(arr: T[], fn: PropertyKey | Iteratee = id) {
|
|
923
|
+
const keys = new Set<T>()
|
|
924
|
+
const mapper = typeof fn === 'function' ? fn : (x) => x?.[fn]
|
|
925
|
+
for (const x of arr) {
|
|
926
|
+
keys.add(mapper(x))
|
|
927
|
+
}
|
|
928
|
+
return Array.from(keys.values())
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* This method is like {@link uniq} except that it sorts the results in ascending order
|
|
933
|
+
* @param arr The array containing duplicated elements
|
|
934
|
+
* @param fn The iteratee invoked per element.
|
|
935
|
+
* @returns Returns the new duplicate free array
|
|
936
|
+
* @example
|
|
937
|
+
```js
|
|
938
|
+
uniq([2, 1, 2])
|
|
939
|
+
// => [1, 2]
|
|
940
|
+
```
|
|
941
|
+
* @see {@link uniq}
|
|
942
|
+
*/
|
|
943
|
+
export function sortedUniq(arr, fn: string | Iteratee = id) {
|
|
944
|
+
const values = uniq(arr, fn)
|
|
945
|
+
values.sort(comp)
|
|
946
|
+
return values
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function baseMergeDeep(obj, source, key, stack) {
|
|
950
|
+
const objValue = obj[key]
|
|
951
|
+
const srcValue = source[key]
|
|
952
|
+
const stacked = stack.get(srcValue)
|
|
953
|
+
if (stacked && (objValue !== stacked || !(key in obj))) {
|
|
954
|
+
obj[key] = stacked
|
|
955
|
+
return
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
let newValue: any
|
|
959
|
+
let isCommon = newValue === undefined
|
|
960
|
+
const isArray = Array.isArray(srcValue)
|
|
961
|
+
const isTyped = ArrayBuffer.isView(srcValue)
|
|
962
|
+
|
|
963
|
+
if (isArray || isTyped) {
|
|
964
|
+
if (Array.isArray(objValue)) {
|
|
965
|
+
newValue = objValue
|
|
966
|
+
} else if (isArrayLike(objValue)) {
|
|
967
|
+
newValue = Array.from(objValue)
|
|
968
|
+
} else if (isTyped) {
|
|
969
|
+
isCommon = false
|
|
970
|
+
newValue = cloneTypedArray(srcValue, true)
|
|
971
|
+
} else {
|
|
972
|
+
newValue = []
|
|
973
|
+
}
|
|
974
|
+
} else if (isObject(srcValue)) {
|
|
975
|
+
newValue = objValue
|
|
976
|
+
if (!isObject(objValue)) {
|
|
977
|
+
newValue = srcValue
|
|
978
|
+
}
|
|
979
|
+
} else {
|
|
980
|
+
isCommon = false
|
|
981
|
+
}
|
|
982
|
+
if (isCommon) {
|
|
983
|
+
stack.set(srcValue, newValue)
|
|
984
|
+
baseMerge(newValue, srcValue, stack)
|
|
985
|
+
stack.delete(srcValue)
|
|
986
|
+
}
|
|
987
|
+
obj[key] = newValue
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
function baseMerge(obj, source, stack?) {
|
|
991
|
+
if (obj === source) return obj
|
|
992
|
+
for (const key of Object.keys(source)) {
|
|
993
|
+
const srcValue = source[key]
|
|
994
|
+
const objValue = obj[key]
|
|
995
|
+
if (typeof srcValue === 'object' && srcValue !== null) {
|
|
996
|
+
baseMergeDeep(obj, source, key, stack || new WeakMap())
|
|
997
|
+
} else {
|
|
998
|
+
if (objValue !== srcValue) {
|
|
999
|
+
obj[key] = srcValue
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Recursively merges own and inherited enumerable string keyed properties of source objects into the destination object. Source properties that resolve to `undefined` are skipped if a destination value exists. Array and plain object properties are merged recursively. Other objects and value types are overridden by assignment. Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
|
|
1007
|
+
*
|
|
1008
|
+
* Note: this method mutates `object`
|
|
1009
|
+
*
|
|
1010
|
+
*
|
|
1011
|
+
* @param {Object} object The destination object.
|
|
1012
|
+
* @param {...Object} sources The source objects.
|
|
1013
|
+
* @returns Returns `object`.
|
|
1014
|
+
*
|
|
1015
|
+
* @example
|
|
1016
|
+
```js
|
|
1017
|
+
let object = {
|
|
1018
|
+
'a': [{ 'b': 2 }, { 'd': 4 }]
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
let other = {
|
|
1022
|
+
'a': [{ 'c': 3 }, { 'e': 5 }]
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
merge(object, other)
|
|
1026
|
+
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
|
|
1027
|
+
```
|
|
1028
|
+
*/
|
|
1029
|
+
export function merge(object, ...sources): Object {
|
|
1030
|
+
for (const source of sources) {
|
|
1031
|
+
baseMerge(object, source)
|
|
1032
|
+
}
|
|
1033
|
+
return object
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* Returns a generator of array values not included in the other given arrays using a `Set` for equality comparisons. The order and references of result values are not guaranteed.
|
|
1038
|
+
* @param args The initial arrays
|
|
1039
|
+
* @example
|
|
1040
|
+
```js
|
|
1041
|
+
[...difference([2, 1], [2, 3])]
|
|
1042
|
+
// => [1]
|
|
1043
|
+
```
|
|
1044
|
+
* @see {@link union}
|
|
1045
|
+
* @see {@link intersection}
|
|
1046
|
+
*/
|
|
1047
|
+
export function* difference(...args: Array<Iterable<any>>) {
|
|
1048
|
+
const sets = args.map((arr) => new Set(arr))
|
|
1049
|
+
const setA = sets[0]
|
|
1050
|
+
|
|
1051
|
+
for (let i = 1; i < sets.length; i++) {
|
|
1052
|
+
for (const x of sets[i].values()) {
|
|
1053
|
+
if (!setA.delete(x)) setA.add(x)
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
for (const x of setA.values()) {
|
|
1058
|
+
yield x
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Creates a generator of unique values that are included in all given arrays.
|
|
1064
|
+
* @param args The arrays to inspect
|
|
1065
|
+
* @example
|
|
1066
|
+
```js
|
|
1067
|
+
[...intersection([2, 1], [2, 3])]
|
|
1068
|
+
// => [2]
|
|
1069
|
+
```
|
|
1070
|
+
* @see {@link difference}
|
|
1071
|
+
* @see {@link union}
|
|
1072
|
+
*/
|
|
1073
|
+
export function* intersection(...args: Array<Iterable<any>>) {
|
|
1074
|
+
const sets = args.map((arr) => new Set(arr))
|
|
1075
|
+
// build a counter map to find items in all
|
|
1076
|
+
const results = new Map()
|
|
1077
|
+
const total = sets.length
|
|
1078
|
+
|
|
1079
|
+
for (let i = 0; i < total; i++) {
|
|
1080
|
+
for (const x of sets[i].values()) {
|
|
1081
|
+
const count = results.get(x) || 0
|
|
1082
|
+
results.set(x, count + 1)
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
for (const [key, value] of results.entries()) {
|
|
1087
|
+
if (value === total) yield key
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Creates a generator of unique values from all given arrays using `Set` for equality comparisons.
|
|
1093
|
+
* @param args The arrays to perform union on.
|
|
1094
|
+
* @example
|
|
1095
|
+
```js
|
|
1096
|
+
[...union([2], [1, 2])]
|
|
1097
|
+
// => [2, 1]
|
|
1098
|
+
```
|
|
1099
|
+
* @see {@link difference}
|
|
1100
|
+
* @see {@link intersection}
|
|
1101
|
+
*/
|
|
1102
|
+
export function* union(...args: Array<Iterable<any>>) {
|
|
1103
|
+
const results = new Set()
|
|
1104
|
+
for (const arr of args) {
|
|
1105
|
+
for (const item of arr) {
|
|
1106
|
+
results.add(item)
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
for (const item of results.values()) {
|
|
1110
|
+
yield item
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Creates an object composed of keys generated from the results of running each element of `arr` thru `func`. The order of grouped values is determined by the order they occur in `arr`. The corresponding value of each key is an array of elements responsible for generating the key.
|
|
1116
|
+
*
|
|
1117
|
+
* @param arr The collection to iterate over.
|
|
1118
|
+
* @param [func=id] The iteratee to transform keys.
|
|
1119
|
+
* @returns Returns the composed aggregate object.
|
|
1120
|
+
*
|
|
1121
|
+
* @example
|
|
1122
|
+
```js
|
|
1123
|
+
groupBy([6.1, 4.2, 6.3], Math.floor)
|
|
1124
|
+
// => { '4': [4.2], '6': [6.1, 6.3] }
|
|
1125
|
+
|
|
1126
|
+
// The `property` iteratee shorthand.
|
|
1127
|
+
groupBy(['one', 'two', 'three'], 'length')
|
|
1128
|
+
// => { '3': ['one', 'two'], '5': ['three'] }
|
|
1129
|
+
```
|
|
1130
|
+
*/
|
|
1131
|
+
export function groupBy(arr: Iterable<any> | Object, func: Iteratee | PropertyKey = id): Object {
|
|
1132
|
+
const results = {}
|
|
1133
|
+
const useKey = typeof func === 'function' ? func : partial(get, func)
|
|
1134
|
+
forEach(arr, (value) => {
|
|
1135
|
+
const groupKey = useKey(value)
|
|
1136
|
+
const values = results[groupKey] || []
|
|
1137
|
+
values.push(value)
|
|
1138
|
+
results[groupKey] = values
|
|
1139
|
+
})
|
|
1140
|
+
return results
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Removes all elements from array that `func` returns truthy for and returns an array of the removed elements.
|
|
1145
|
+
* @param arr The array to remove from.
|
|
1146
|
+
* @param func The function invoked per iteration or value(s) or value to remove.
|
|
1147
|
+
* @returns
|
|
1148
|
+
*/
|
|
1149
|
+
export function remove<T>(arr: T[], func: any) {
|
|
1150
|
+
return arr.filter((x, i) => {
|
|
1151
|
+
if (isArray(func)) return !func.includes(x)
|
|
1152
|
+
else if (isFunc(func)) return !func(x, i, arr)
|
|
1153
|
+
return x !== func
|
|
1154
|
+
})
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// TODO: binary search utils
|
|
1158
|
+
// TODO: heap/heapify
|
|
1159
|
+
// TODO: deque
|
|
1160
|
+
// TODO: Counter
|
|
1161
|
+
// TODO: Skip list
|
|
1162
|
+
// TODO: BST (BTree)
|