@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.5.4",
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
- * Shorthand for returning all unique elements in an array.
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
- return [...new Set(array)]
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("./array.js")
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", () => {