@tim-code/my-util 0.5.4 → 0.5.6
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/package.json +5 -2
- package/src/array.js +73 -3
- package/src/array.test.js +258 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tim-code/my-util",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tim Sprowl",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@tim-code/eslint-config": "^1.3.3",
|
|
32
31
|
"@jest/globals": "^29.7.0",
|
|
32
|
+
"@tim-code/eslint-config": "^1.3.3",
|
|
33
33
|
"jest": "^29.7.0"
|
|
34
34
|
},
|
|
35
35
|
"jest": {
|
|
@@ -37,5 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@tim-code/my-util": "^0.5.5"
|
|
40
43
|
}
|
|
41
44
|
}
|
package/src/array.js
CHANGED
|
@@ -20,12 +20,82 @@ export function chunk(array, chunkSize = array.length) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Returns all unique elements in an array, or alternatively by checking a key's value or function's result.
|
|
24
24
|
* @param {Array} array
|
|
25
|
+
* @param {Object} $1
|
|
26
|
+
* @param {string|number|Function=} $1.key
|
|
27
|
+
* If a function, calls the provided function on an element to get the value to check for uniqueness.
|
|
28
|
+
* If a string or number, checks each element's value at key for uniqueness.
|
|
25
29
|
* @returns {Array}
|
|
26
30
|
*/
|
|
27
|
-
export function unique(array) {
|
|
28
|
-
|
|
31
|
+
export function unique(array, { key } = {}) {
|
|
32
|
+
const seen = new Set()
|
|
33
|
+
const result = []
|
|
34
|
+
if (typeof key === "function") {
|
|
35
|
+
for (let i = 0; i < array.length; i++) {
|
|
36
|
+
const element = array[i]
|
|
37
|
+
const value = key(element, i, array)
|
|
38
|
+
if (!seen.has(value)) {
|
|
39
|
+
seen.add(value)
|
|
40
|
+
result.push(element)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else if (typeof key === "string" || typeof key === "number") {
|
|
44
|
+
for (const element of array) {
|
|
45
|
+
const value = element[key]
|
|
46
|
+
if (!seen.has(value)) {
|
|
47
|
+
seen.add(value)
|
|
48
|
+
result.push(element)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
return [...new Set(array)]
|
|
53
|
+
}
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns groups of duplicate elements in an array.
|
|
59
|
+
* Each group is an array of elements that share the same key or callback result.
|
|
60
|
+
* Only groups with more than one element are returned. Returns an empty array if no duplicates.
|
|
61
|
+
* @param {Array} array
|
|
62
|
+
* @param {Object} $1
|
|
63
|
+
* @param {string|number|Function=} $1.key
|
|
64
|
+
* If a function, calls the provided function on an element to get the value for grouping.
|
|
65
|
+
* If a string or number, uses element[key].
|
|
66
|
+
* If omitted, compares elements directly.
|
|
67
|
+
* @returns {Array<Array>}
|
|
68
|
+
*/
|
|
69
|
+
export function duplicates(array, { key } = {}) {
|
|
70
|
+
const groups = new Map()
|
|
71
|
+
|
|
72
|
+
if (typeof key === "function") {
|
|
73
|
+
for (let i = 0; i < array.length; i++) {
|
|
74
|
+
const element = array[i]
|
|
75
|
+
const value = key(element, i, array)
|
|
76
|
+
if (!groups.has(value)) {
|
|
77
|
+
groups.set(value, [])
|
|
78
|
+
}
|
|
79
|
+
groups.get(value).push(element)
|
|
80
|
+
}
|
|
81
|
+
} else if (typeof key === "string" || typeof key === "number") {
|
|
82
|
+
for (const element of array) {
|
|
83
|
+
const value = element[key]
|
|
84
|
+
if (!groups.has(value)) {
|
|
85
|
+
groups.set(value, [])
|
|
86
|
+
}
|
|
87
|
+
groups.get(value).push(element)
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
for (const element of array) {
|
|
91
|
+
if (!groups.has(element)) {
|
|
92
|
+
groups.set(element, [])
|
|
93
|
+
}
|
|
94
|
+
groups.get(element).push(element)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const results = [...groups.values()].filter((group) => group.length > 1)
|
|
98
|
+
return results
|
|
29
99
|
}
|
|
30
100
|
|
|
31
101
|
// sorts undefined and null to the end if applicable
|
package/src/array.test.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/* eslint-disable no-restricted-syntax */
|
|
2
2
|
import { describe, expect, it, jest } from "@jest/globals"
|
|
3
3
|
|
|
4
|
-
const { chunk, unique, ascending, descending, multilevel } = await import(
|
|
4
|
+
const { chunk, unique, duplicates, ascending, descending, multilevel } = await import(
|
|
5
|
+
"./array.js"
|
|
6
|
+
)
|
|
5
7
|
|
|
6
8
|
describe("chunk", () => {
|
|
7
9
|
it("splits array into chunks of specified size", () => {
|
|
@@ -68,6 +70,261 @@ describe("unique", () => {
|
|
|
68
70
|
const b = {}
|
|
69
71
|
expect(unique([a, b, a])).toEqual([a, b])
|
|
70
72
|
})
|
|
73
|
+
|
|
74
|
+
it("returns unique elements by key (string)", () => {
|
|
75
|
+
const arr = [
|
|
76
|
+
{ id: 1, name: "a" },
|
|
77
|
+
{ id: 2, name: "b" },
|
|
78
|
+
{ id: 1, name: "c" },
|
|
79
|
+
{ id: 3, name: "d" },
|
|
80
|
+
{ id: 2, name: "e" },
|
|
81
|
+
]
|
|
82
|
+
expect(unique(arr, { key: "id" })).toEqual([
|
|
83
|
+
{ id: 1, name: "a" },
|
|
84
|
+
{ id: 2, name: "b" },
|
|
85
|
+
{ id: 3, name: "d" },
|
|
86
|
+
])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("returns unique elements by key (number)", () => {
|
|
90
|
+
const arr = [
|
|
91
|
+
{ 0: "a", v: 1 },
|
|
92
|
+
{ 0: "b", v: 2 },
|
|
93
|
+
{ 0: "a", v: 3 },
|
|
94
|
+
{ 0: "c", v: 4 },
|
|
95
|
+
]
|
|
96
|
+
expect(unique(arr, { key: 0 })).toEqual([
|
|
97
|
+
{ 0: "a", v: 1 },
|
|
98
|
+
{ 0: "b", v: 2 },
|
|
99
|
+
{ 0: "c", v: 4 },
|
|
100
|
+
])
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("returns unique elements by function", () => {
|
|
104
|
+
const arr = [
|
|
105
|
+
{ id: 1, name: "a" },
|
|
106
|
+
{ id: 2, name: "b" },
|
|
107
|
+
{ id: 1, name: "c" },
|
|
108
|
+
{ id: 3, name: "d" },
|
|
109
|
+
{ id: 2, name: "e" },
|
|
110
|
+
]
|
|
111
|
+
expect(
|
|
112
|
+
unique(arr, {
|
|
113
|
+
key: (el) => el.id % 2, // group by odd/even id
|
|
114
|
+
})
|
|
115
|
+
).toEqual([
|
|
116
|
+
{ id: 1, name: "a" }, // id % 2 === 1
|
|
117
|
+
{ id: 2, name: "b" }, // id % 2 === 0
|
|
118
|
+
])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it("returns unique elements by function using index and array", () => {
|
|
122
|
+
const arr = ["a", "b", "c", "a"]
|
|
123
|
+
expect(
|
|
124
|
+
unique(arr, {
|
|
125
|
+
key: (el, i, array) => array.indexOf(el), // only first occurrence is unique
|
|
126
|
+
})
|
|
127
|
+
).toEqual(["a", "b", "c"])
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("returns unique elements by key when some elements lack the key", () => {
|
|
131
|
+
const arr = [{ id: 1 }, {}, { id: 1 }, { id: 2 }, {}]
|
|
132
|
+
expect(unique(arr, { key: "id" })).toEqual([{ id: 1 }, {}, { id: 2 }])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it("returns unique elements by function when function returns undefined/null", () => {
|
|
136
|
+
const arr = [{ id: 1 }, {}, { id: 2 }, { id: null }, {}]
|
|
137
|
+
expect(
|
|
138
|
+
unique(arr, {
|
|
139
|
+
key: (el) => el.id,
|
|
140
|
+
})
|
|
141
|
+
).toEqual([{ id: 1 }, {}, { id: 2 }, { id: null }])
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("returns unique elements by key when key value is undefined/null", () => {
|
|
145
|
+
const arr = [{ id: 1 }, { id: undefined }, { id: 2 }, { id: null }, { id: undefined }]
|
|
146
|
+
expect(unique(arr, { key: "id" })).toEqual([
|
|
147
|
+
{ id: 1 },
|
|
148
|
+
{ id: undefined },
|
|
149
|
+
{ id: 2 },
|
|
150
|
+
{ id: null },
|
|
151
|
+
])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("returns unique elements for primitive array if key is not provided", () => {
|
|
155
|
+
expect(unique([1, 2, 2, 3], {})).toEqual([1, 2, 3])
|
|
156
|
+
expect(unique([1, 2, 2, 3], { key: undefined })).toEqual([1, 2, 3])
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("returns unique elements for object array if key is not provided", () => {
|
|
160
|
+
const a = { x: 1 }
|
|
161
|
+
const b = { x: 2 }
|
|
162
|
+
expect(unique([a, b, a], {})).toEqual([a, b])
|
|
163
|
+
expect(unique([a, b, a], { key: undefined })).toEqual([a, b])
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
describe("duplicates", () => {
|
|
168
|
+
it("returns empty array if there are no duplicates", () => {
|
|
169
|
+
expect(duplicates([1, 2, 3])).toEqual([])
|
|
170
|
+
expect(duplicates([], {})).toEqual([])
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("returns groups of duplicate primitives", () => {
|
|
174
|
+
expect(duplicates([1, 2, 1, 3, 2, 1])).toEqual([
|
|
175
|
+
[1, 1, 1],
|
|
176
|
+
[2, 2],
|
|
177
|
+
])
|
|
178
|
+
expect(duplicates(["a", "b", "a", "c", "b"])).toEqual([
|
|
179
|
+
["a", "a"],
|
|
180
|
+
["b", "b"],
|
|
181
|
+
])
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("returns groups of duplicate objects by reference", () => {
|
|
185
|
+
const a = {}
|
|
186
|
+
const b = {}
|
|
187
|
+
expect(duplicates([a, b, a, b, a])).toEqual([
|
|
188
|
+
[a, a, a],
|
|
189
|
+
[b, b],
|
|
190
|
+
])
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("returns groups of duplicates by key (string)", () => {
|
|
194
|
+
const arr = [
|
|
195
|
+
{ id: 1, name: "a" },
|
|
196
|
+
{ id: 2, name: "b" },
|
|
197
|
+
{ id: 1, name: "c" },
|
|
198
|
+
{ id: 3, name: "d" },
|
|
199
|
+
{ id: 2, name: "e" },
|
|
200
|
+
{ id: 1, name: "f" },
|
|
201
|
+
]
|
|
202
|
+
expect(duplicates(arr, { key: "id" })).toEqual([
|
|
203
|
+
[
|
|
204
|
+
{ id: 1, name: "a" },
|
|
205
|
+
{ id: 1, name: "c" },
|
|
206
|
+
{ id: 1, name: "f" },
|
|
207
|
+
],
|
|
208
|
+
[
|
|
209
|
+
{ id: 2, name: "b" },
|
|
210
|
+
{ id: 2, name: "e" },
|
|
211
|
+
],
|
|
212
|
+
])
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it("returns groups of duplicates by key (number)", () => {
|
|
216
|
+
const arr = [
|
|
217
|
+
{ 0: "a", v: 1 },
|
|
218
|
+
{ 0: "b", v: 2 },
|
|
219
|
+
{ 0: "a", v: 3 },
|
|
220
|
+
{ 0: "c", v: 4 },
|
|
221
|
+
{ 0: "b", v: 5 },
|
|
222
|
+
]
|
|
223
|
+
expect(duplicates(arr, { key: 0 })).toEqual([
|
|
224
|
+
[
|
|
225
|
+
{ 0: "a", v: 1 },
|
|
226
|
+
{ 0: "a", v: 3 },
|
|
227
|
+
],
|
|
228
|
+
[
|
|
229
|
+
{ 0: "b", v: 2 },
|
|
230
|
+
{ 0: "b", v: 5 },
|
|
231
|
+
],
|
|
232
|
+
])
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("returns groups of duplicates by function", () => {
|
|
236
|
+
const arr = [
|
|
237
|
+
{ id: 1, name: "a" },
|
|
238
|
+
{ id: 2, name: "b" },
|
|
239
|
+
{ id: 1, name: "c" },
|
|
240
|
+
{ id: 3, name: "d" },
|
|
241
|
+
{ id: 2, name: "e" },
|
|
242
|
+
{ id: 1, name: "f" },
|
|
243
|
+
]
|
|
244
|
+
expect(
|
|
245
|
+
duplicates(arr, {
|
|
246
|
+
key: (el) => el.id % 2, // group by odd/even id
|
|
247
|
+
})
|
|
248
|
+
).toEqual([
|
|
249
|
+
[
|
|
250
|
+
{ id: 1, name: "a" },
|
|
251
|
+
{ id: 1, name: "c" },
|
|
252
|
+
{ id: 3, name: "d" },
|
|
253
|
+
{ id: 1, name: "f" },
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
{ id: 2, name: "b" },
|
|
257
|
+
{ id: 2, name: "e" },
|
|
258
|
+
],
|
|
259
|
+
])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("returns groups of duplicates by function using index and array", () => {
|
|
263
|
+
const arr = ["a", "b", "c", "a", "c"]
|
|
264
|
+
expect(
|
|
265
|
+
duplicates(arr, {
|
|
266
|
+
key: (el, i, array) => array.indexOf(el), // group by first occurrence index
|
|
267
|
+
})
|
|
268
|
+
).toEqual([
|
|
269
|
+
["a", "a"],
|
|
270
|
+
["c", "c"],
|
|
271
|
+
])
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it("returns groups of duplicates by key when some elements lack the key", () => {
|
|
275
|
+
const arr = [{ id: 1 }, {}, { id: 1 }, { id: 2 }, {}, { id: 2 }]
|
|
276
|
+
expect(duplicates(arr, { key: "id" })).toEqual([
|
|
277
|
+
[{ id: 1 }, { id: 1 }],
|
|
278
|
+
[{}, {}],
|
|
279
|
+
[{ id: 2 }, { id: 2 }],
|
|
280
|
+
])
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it("returns groups of duplicates by function when function returns undefined/null", () => {
|
|
284
|
+
const arr = [{ id: 1 }, {}, { id: 2 }, { id: null }, {}, { id: null }]
|
|
285
|
+
expect(
|
|
286
|
+
duplicates(arr, {
|
|
287
|
+
key: (el) => el.id,
|
|
288
|
+
})
|
|
289
|
+
).toEqual([
|
|
290
|
+
[{}, {}],
|
|
291
|
+
[{ id: null }, { id: null }],
|
|
292
|
+
])
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it("returns groups of duplicates by key when key value is undefined/null", () => {
|
|
296
|
+
const arr = [
|
|
297
|
+
{ id: 1 },
|
|
298
|
+
{ id: undefined },
|
|
299
|
+
{ id: 2 },
|
|
300
|
+
{ id: null },
|
|
301
|
+
{ id: undefined },
|
|
302
|
+
{ id: null },
|
|
303
|
+
]
|
|
304
|
+
expect(duplicates(arr, { key: "id" })).toEqual([
|
|
305
|
+
[{ id: undefined }, { id: undefined }],
|
|
306
|
+
[{ id: null }, { id: null }],
|
|
307
|
+
])
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it("returns empty array if all elements are unique by key or function", () => {
|
|
311
|
+
const arr = [{ id: 1 }, { id: 2 }, { id: 3 }]
|
|
312
|
+
expect(duplicates(arr, { key: "id" })).toEqual([])
|
|
313
|
+
expect(
|
|
314
|
+
duplicates(arr, {
|
|
315
|
+
key: (el) => el.id,
|
|
316
|
+
})
|
|
317
|
+
).toEqual([])
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it("returns empty array for empty input with key or function", () => {
|
|
321
|
+
expect(duplicates([], { key: "id" })).toEqual([])
|
|
322
|
+
expect(
|
|
323
|
+
duplicates([], {
|
|
324
|
+
key: (el) => el,
|
|
325
|
+
})
|
|
326
|
+
).toEqual([])
|
|
327
|
+
})
|
|
71
328
|
})
|
|
72
329
|
|
|
73
330
|
describe("ascending", () => {
|