@tim-code/my-util 0.1.4 → 0.2.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.
- package/package.json +1 -1
- package/src/find.js +142 -63
- package/src/find.test.js +199 -248
package/package.json
CHANGED
package/src/find.js
CHANGED
|
@@ -1,150 +1,150 @@
|
|
|
1
|
-
export function findClosestAbs(array, desired, { key,
|
|
1
|
+
export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {}) {
|
|
2
2
|
let closest
|
|
3
|
-
if (
|
|
3
|
+
if (typeof key === "function") {
|
|
4
4
|
for (let i = 0; i < array.length; i++) {
|
|
5
5
|
const element = array[i]
|
|
6
|
-
const value =
|
|
6
|
+
const value = key(element, i, array)
|
|
7
7
|
const diff = Math.abs(value - desired)
|
|
8
|
-
if (diff <
|
|
8
|
+
if (diff < cutoff) {
|
|
9
9
|
closest = element
|
|
10
|
-
|
|
10
|
+
cutoff = diff
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
} else if (key) {
|
|
13
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
14
14
|
for (const element of array) {
|
|
15
15
|
const value = element[key]
|
|
16
16
|
const diff = Math.abs(value - desired)
|
|
17
|
-
if (diff <
|
|
17
|
+
if (diff < cutoff) {
|
|
18
18
|
closest = element
|
|
19
|
-
|
|
19
|
+
cutoff = diff
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
} else {
|
|
23
23
|
for (const value of array) {
|
|
24
24
|
const diff = Math.abs(value - desired)
|
|
25
|
-
if (diff <
|
|
25
|
+
if (diff < cutoff) {
|
|
26
26
|
closest = value
|
|
27
|
-
|
|
27
|
+
cutoff = diff
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
return closest
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function findClosestLT(array, desired, { key,
|
|
34
|
+
export function findClosestLT(array, desired, { key, cutoff = -Infinity } = {}) {
|
|
35
35
|
let closest
|
|
36
|
-
if (
|
|
36
|
+
if (typeof key === "function") {
|
|
37
37
|
for (let i = 0; i < array.length; i++) {
|
|
38
38
|
const element = array[i]
|
|
39
|
-
const value =
|
|
40
|
-
if (value < desired && value >
|
|
39
|
+
const value = key(element, i, array)
|
|
40
|
+
if (value < desired && value > cutoff) {
|
|
41
41
|
closest = element
|
|
42
|
-
|
|
42
|
+
cutoff = value
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
} else if (key) {
|
|
45
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
46
46
|
for (const element of array) {
|
|
47
47
|
const value = element[key]
|
|
48
|
-
if (value < desired && value >
|
|
48
|
+
if (value < desired && value > cutoff) {
|
|
49
49
|
closest = element
|
|
50
|
-
|
|
50
|
+
cutoff = value
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
} else {
|
|
54
54
|
for (const value of array) {
|
|
55
|
-
if (value < desired && value >
|
|
55
|
+
if (value < desired && value > cutoff) {
|
|
56
56
|
closest = value
|
|
57
|
-
|
|
57
|
+
cutoff = value
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
return closest
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function findClosestLTE(array, desired, { key,
|
|
64
|
+
export function findClosestLTE(array, desired, { key, cutoff = -Infinity } = {}) {
|
|
65
65
|
let closest
|
|
66
|
-
if (
|
|
66
|
+
if (typeof key === "function") {
|
|
67
67
|
for (let i = 0; i < array.length; i++) {
|
|
68
68
|
const element = array[i]
|
|
69
|
-
const value =
|
|
70
|
-
if (value <= desired && value >
|
|
69
|
+
const value = key(element, i, array)
|
|
70
|
+
if (value <= desired && value > cutoff) {
|
|
71
71
|
closest = element
|
|
72
|
-
|
|
72
|
+
cutoff = value
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
} else if (key) {
|
|
75
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
76
76
|
for (const element of array) {
|
|
77
77
|
const value = element[key]
|
|
78
|
-
if (value <= desired && value >
|
|
78
|
+
if (value <= desired && value > cutoff) {
|
|
79
79
|
closest = element
|
|
80
|
-
|
|
80
|
+
cutoff = value
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
} else {
|
|
84
84
|
for (const value of array) {
|
|
85
|
-
if (value <= desired && value >
|
|
85
|
+
if (value <= desired && value > cutoff) {
|
|
86
86
|
closest = value
|
|
87
|
-
|
|
87
|
+
cutoff = value
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
return closest
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export function findClosestGT(array, desired, { key,
|
|
94
|
+
export function findClosestGT(array, desired, { key, cutoff = Infinity } = {}) {
|
|
95
95
|
let closest
|
|
96
|
-
if (
|
|
96
|
+
if (typeof key === "function") {
|
|
97
97
|
for (let i = 0; i < array.length; i++) {
|
|
98
98
|
const element = array[i]
|
|
99
|
-
const value =
|
|
100
|
-
if (value > desired && value <
|
|
99
|
+
const value = key(element, i, array)
|
|
100
|
+
if (value > desired && value < cutoff) {
|
|
101
101
|
closest = element
|
|
102
|
-
|
|
102
|
+
cutoff = value
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
} else if (key) {
|
|
105
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
106
106
|
for (const element of array) {
|
|
107
107
|
const value = element[key]
|
|
108
|
-
if (value > desired && value <
|
|
108
|
+
if (value > desired && value < cutoff) {
|
|
109
109
|
closest = element
|
|
110
|
-
|
|
110
|
+
cutoff = value
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
} else {
|
|
114
114
|
for (const value of array) {
|
|
115
|
-
if (value > desired && value <
|
|
115
|
+
if (value > desired && value < cutoff) {
|
|
116
116
|
closest = value
|
|
117
|
-
|
|
117
|
+
cutoff = value
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
return closest
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
export function findClosestGTE(array, desired, { key,
|
|
124
|
+
export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {}) {
|
|
125
125
|
let closest
|
|
126
|
-
if (
|
|
126
|
+
if (typeof key === "function") {
|
|
127
127
|
for (let i = 0; i < array.length; i++) {
|
|
128
128
|
const element = array[i]
|
|
129
|
-
const value =
|
|
130
|
-
if (value >= desired && value <
|
|
129
|
+
const value = key(element, i, array)
|
|
130
|
+
if (value >= desired && value < cutoff) {
|
|
131
131
|
closest = element
|
|
132
|
-
|
|
132
|
+
cutoff = value
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
|
-
} else if (key) {
|
|
135
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
136
136
|
for (const element of array) {
|
|
137
137
|
const value = element[key]
|
|
138
|
-
if (value >= desired && value <
|
|
138
|
+
if (value >= desired && value < cutoff) {
|
|
139
139
|
closest = element
|
|
140
|
-
|
|
140
|
+
cutoff = value
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
} else {
|
|
144
144
|
for (const value of array) {
|
|
145
|
-
if (value >= desired && value <
|
|
145
|
+
if (value >= desired && value < cutoff) {
|
|
146
146
|
closest = value
|
|
147
|
-
|
|
147
|
+
cutoff = value
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
}
|
|
@@ -152,27 +152,24 @@ export function findClosestGTE(array, desired, { key, map, threshold = Infinity
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
* Find the closest element in an array.
|
|
156
|
-
* If using for strings, need to specify different values for "
|
|
157
|
-
* "~" and "" are good
|
|
155
|
+
* Find the closest element in an array. If there is a tie, then returns the first matching element by order in the array.
|
|
156
|
+
* If using for strings, need to specify different values for "cutoff" and "comparator".
|
|
157
|
+
* "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
|
|
158
158
|
* @template T, V
|
|
159
159
|
* @param {Array<T>} array
|
|
160
160
|
* @param {V} value The desired value to search for
|
|
161
161
|
* @param {Object} options
|
|
162
|
-
* @param {string|number=} options.key
|
|
163
|
-
*
|
|
164
|
-
*
|
|
162
|
+
* @param {string|number|Function=} options.key
|
|
163
|
+
* If specified, will consider the value for each element's key instead of the element itself.
|
|
164
|
+
* If a function, called with the element, index and array (same as .map() callback) to produce the value to sort on.
|
|
165
165
|
* @param {string=} options.comparator "abs", "lt", "lte", "gt", "gte", "abs". Default is "abs" which implies T is number.
|
|
166
|
-
* @param {V=} options.
|
|
166
|
+
* @param {V=} options.cutoff If specified, sets a initial constraint on how close the found value must be.
|
|
167
|
+
* For example, if used with "lt", the found element would need to be greater than the cutoff but still less than the desired value.
|
|
168
|
+
* If used with "abs", the found element would need to have a difference with the desired value less than the cutoff.
|
|
167
169
|
* @returns {T|undefined}
|
|
168
170
|
*/
|
|
169
171
|
export function findClosest(array, value, options = {}) {
|
|
170
|
-
const { comparator = "abs"
|
|
171
|
-
if (typeof transform === "function") {
|
|
172
|
-
options = { ...options, map: transform }
|
|
173
|
-
} else if (typeof transform === "string" || typeof transform === "number") {
|
|
174
|
-
options = { ...options, key: transform }
|
|
175
|
-
}
|
|
172
|
+
const { comparator = "abs" } = options
|
|
176
173
|
switch (comparator) {
|
|
177
174
|
case "lt":
|
|
178
175
|
return findClosestLT(array, value, options)
|
|
@@ -188,3 +185,85 @@ export function findClosest(array, value, options = {}) {
|
|
|
188
185
|
throw new Error(`Unknown comparator: ${comparator}`)
|
|
189
186
|
}
|
|
190
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Find the minimum value in an array.
|
|
191
|
+
* @template T, V
|
|
192
|
+
* @param {Array<T>} array
|
|
193
|
+
* @param {Object} $1
|
|
194
|
+
* @param {string|Function=} $1.key Specifies an alternative to using each element as the value.
|
|
195
|
+
* If string, then accesses each element at that key to get value.
|
|
196
|
+
* If function, then calls the callback on each element to get value.
|
|
197
|
+
* @param {V=} $1.cutoff Only values below cutoff will be considered.
|
|
198
|
+
* @returns {T}
|
|
199
|
+
*/
|
|
200
|
+
export function findMin(array, { key, cutoff = Infinity } = {}) {
|
|
201
|
+
let closest
|
|
202
|
+
if (typeof key === "function") {
|
|
203
|
+
for (let i = 0; i < array.length; i++) {
|
|
204
|
+
const element = array[i]
|
|
205
|
+
const value = key(element, i, array)
|
|
206
|
+
if (value < cutoff) {
|
|
207
|
+
closest = element
|
|
208
|
+
cutoff = value
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
212
|
+
for (const element of array) {
|
|
213
|
+
const value = element[key]
|
|
214
|
+
if (value < cutoff) {
|
|
215
|
+
closest = element
|
|
216
|
+
cutoff = value
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
for (const value of array) {
|
|
221
|
+
if (value < cutoff) {
|
|
222
|
+
closest = value
|
|
223
|
+
cutoff = value
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return closest
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Find the maximum value in an array.
|
|
232
|
+
* @template T, V
|
|
233
|
+
* @param {Array<T>} array
|
|
234
|
+
* @param {Object} $1
|
|
235
|
+
* @param {string|Function=} $1.key Specifies an alternative to using each element as the value.
|
|
236
|
+
* If string, then accesses each element at that key to get value.
|
|
237
|
+
* If function, then calls the callback on each element to get value.
|
|
238
|
+
* @param {V=} $1.cutoff Only values above cutoff will be considered.
|
|
239
|
+
* @returns {T}
|
|
240
|
+
*/
|
|
241
|
+
export function findMax(array, { key, cutoff = -Infinity } = {}) {
|
|
242
|
+
let closest
|
|
243
|
+
if (typeof key === "function") {
|
|
244
|
+
for (let i = 0; i < array.length; i++) {
|
|
245
|
+
const element = array[i]
|
|
246
|
+
const value = key(element, i, array)
|
|
247
|
+
if (value > cutoff) {
|
|
248
|
+
closest = element
|
|
249
|
+
cutoff = value
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else if (typeof key === "number" || typeof key === "string") {
|
|
253
|
+
for (const element of array) {
|
|
254
|
+
const value = element[key]
|
|
255
|
+
if (value > cutoff) {
|
|
256
|
+
closest = element
|
|
257
|
+
cutoff = value
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
for (const value of array) {
|
|
262
|
+
if (value > cutoff) {
|
|
263
|
+
closest = value
|
|
264
|
+
cutoff = value
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return closest
|
|
269
|
+
}
|
package/src/find.test.js
CHANGED
|
@@ -1,335 +1,286 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { describe, expect, it } from "@jest/globals"
|
|
2
|
+
|
|
3
|
+
const {
|
|
3
4
|
findClosestAbs,
|
|
4
|
-
findClosestGT,
|
|
5
|
-
findClosestGTE,
|
|
6
5
|
findClosestLT,
|
|
7
6
|
findClosestLTE,
|
|
8
|
-
|
|
7
|
+
findClosestGT,
|
|
8
|
+
findClosestGTE,
|
|
9
|
+
findClosest,
|
|
10
|
+
findMin,
|
|
11
|
+
findMax,
|
|
12
|
+
} = await import("./find.js")
|
|
9
13
|
|
|
10
|
-
describe("
|
|
11
|
-
it("returns the closest
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
14
|
-
expect(
|
|
14
|
+
describe("findClosestAbs", () => {
|
|
15
|
+
it("returns the element closest in absolute value to desired", () => {
|
|
16
|
+
expect(findClosestAbs([1, 5, 9], 6)).toBe(5)
|
|
17
|
+
expect(findClosestAbs([1, 5, 9], 8)).toBe(9)
|
|
18
|
+
expect(findClosestAbs([1, 5, 9], 1)).toBe(1)
|
|
15
19
|
})
|
|
16
20
|
|
|
17
|
-
it("returns
|
|
18
|
-
expect(
|
|
21
|
+
it("returns the first element in case of tie", () => {
|
|
22
|
+
expect(findClosestAbs([4, 8], 6)).toBe(4)
|
|
19
23
|
})
|
|
20
24
|
|
|
21
|
-
it("returns
|
|
22
|
-
|
|
23
|
-
expect(findClosest(arr, 6, { key: "v" })).toEqual({ v: 5 })
|
|
25
|
+
it("returns undefined for empty array", () => {
|
|
26
|
+
expect(findClosestAbs([], 10)).toBeUndefined()
|
|
24
27
|
})
|
|
25
28
|
|
|
26
|
-
it("
|
|
27
|
-
|
|
28
|
-
expect(
|
|
29
|
+
it("supports key as function", () => {
|
|
30
|
+
const arr = [{ v: 2 }, { v: 8 }]
|
|
31
|
+
expect(findClosestAbs(arr, 5, { key: (e) => e.v })).toEqual({ v: 2 })
|
|
29
32
|
})
|
|
30
33
|
|
|
31
|
-
it("
|
|
32
|
-
|
|
33
|
-
expect(
|
|
34
|
-
expect(findClosest([1, 3, 5, 7], 0, { comparator: "lte" })).toBeUndefined()
|
|
34
|
+
it("supports key as string", () => {
|
|
35
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
36
|
+
expect(findClosestAbs(arr, 8, { key: "x" })).toEqual({ x: 10 })
|
|
35
37
|
})
|
|
36
38
|
|
|
37
|
-
it("
|
|
38
|
-
|
|
39
|
-
expect(
|
|
39
|
+
it("supports key as number", () => {
|
|
40
|
+
const arr = [[2], [8]]
|
|
41
|
+
expect(findClosestAbs(arr, 7, { key: 0 })).toEqual([8])
|
|
40
42
|
})
|
|
41
43
|
|
|
42
|
-
it("
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
45
|
-
expect(findClosest([1, 3, 5, 7], 8, { comparator: "gte" })).toBeUndefined()
|
|
44
|
+
it("respects cutoff", () => {
|
|
45
|
+
expect(findClosestAbs([1, 5, 9], 6, { cutoff: 2 })).toBe(5)
|
|
46
|
+
expect(findClosestAbs([1, 5, 9], 6, { cutoff: 1 })).toBeUndefined()
|
|
46
47
|
})
|
|
48
|
+
})
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(
|
|
51
|
-
expect(
|
|
52
|
-
expect(
|
|
53
|
-
expect(findClosest(arr, 10, { comparator: "gte", key: "v" })).toEqual({ v: 10 })
|
|
50
|
+
describe("findClosestLT", () => {
|
|
51
|
+
it("returns the closest element less than desired", () => {
|
|
52
|
+
expect(findClosestLT([1, 5, 9], 6)).toBe(5)
|
|
53
|
+
expect(findClosestLT([1, 5, 9], 2)).toBe(1)
|
|
54
|
+
expect(findClosestLT([1, 5, 9], 1)).toBeUndefined()
|
|
54
55
|
})
|
|
55
56
|
|
|
56
|
-
it("
|
|
57
|
-
expect(
|
|
58
|
-
expect(findClosest([1, 5, 10], 6, { threshold: 2 })).toBe(5)
|
|
57
|
+
it("returns first match if tie", () => {
|
|
58
|
+
expect(findClosestLT([2, 2, 1], 3)).toBe(2)
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
it("
|
|
62
|
-
expect(
|
|
63
|
-
expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt", threshold: 5 })).toBeUndefined()
|
|
64
|
-
expect(findClosest([1, 3, 5, 7], 6, { comparator: "gt", threshold: 7 })).toBeUndefined()
|
|
65
|
-
expect(findClosest([1, 3, 5, 7], 6, { comparator: "gt", threshold: 10 })).toBe(7)
|
|
61
|
+
it("returns undefined for empty array", () => {
|
|
62
|
+
expect(findClosestLT([], 10)).toBeUndefined()
|
|
66
63
|
})
|
|
67
64
|
|
|
68
|
-
it("
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
it("supports key as function", () => {
|
|
66
|
+
const arr = [{ v: 2 }, { v: 8 }]
|
|
67
|
+
expect(findClosestLT(arr, 8, { key: (e) => e.v })).toEqual({ v: 2 })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it("supports key as string", () => {
|
|
71
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
72
|
+
expect(findClosestLT(arr, 8, { key: "x" })).toEqual({ x: 1 })
|
|
72
73
|
})
|
|
73
74
|
|
|
74
|
-
it("
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
).
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
it("works with negative numbers and zero", () => {
|
|
84
|
-
expect(findClosest([-10, -5, 0, 5, 10], -7)).toBe(-5)
|
|
85
|
-
expect(findClosest([-10, -5, 0, 5, 10], 0)).toBe(0)
|
|
86
|
-
expect(findClosest([-10, -5, 0, 5, 10], 7)).toBe(5)
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// ISSUE: findClosestAbs and related functions do not skip NaN values in key/map modes, only in value mode.
|
|
90
|
-
it("skips NaN values in abs comparator", () => {
|
|
91
|
-
expect(findClosest([1, NaN, 5], 4)).toBe(5)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it("skips objects missing the key in key-based comparators", () => {
|
|
95
|
-
const arr = [{ v: 1 }, {}, { v: 5 }]
|
|
96
|
-
expect(findClosest(arr, 2, { key: "v" })).toEqual({ v: 1 })
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it("finds the closest string using abs comparator and a custom threshold/comparator", () => {
|
|
100
|
-
// Since abs comparator expects numbers, we need to provide a custom comparator for strings.
|
|
101
|
-
// We'll use threshold and comparator: "lt", "lte", "gt", "gte" for string comparisons.
|
|
102
|
-
const arr = ["apple", "banana", "cherry", "date"]
|
|
103
|
-
// Find the closest string less than "carrot" (alphabetically)
|
|
104
|
-
expect(findClosest(arr, "carrot", { comparator: "lt", threshold: "" })).toBe("banana")
|
|
105
|
-
// Find the closest string less than or equal to "banana"
|
|
106
|
-
expect(findClosest(arr, "banana", { comparator: "lte", threshold: "" })).toBe("banana")
|
|
107
|
-
// Find the closest string greater than "carrot"
|
|
108
|
-
expect(findClosest(arr, "carrot", { comparator: "gt", threshold: "~" })).toBe("cherry")
|
|
109
|
-
// Find the closest string greater than or equal to "date"
|
|
110
|
-
expect(findClosest(arr, "date", { comparator: "gte", threshold: "~" })).toBe("date")
|
|
111
|
-
// If nothing matches, returns undefined
|
|
112
|
-
expect(findClosest(arr, "aardvark", { comparator: "lt", threshold: "" })).toBeUndefined()
|
|
113
|
-
expect(findClosest(arr, "zebra", { comparator: "gt", threshold: "~" })).toBeUndefined()
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it("finds the closest string by key in array of objects", () => {
|
|
117
|
-
const arr = [{ name: "apple" }, { name: "banana" }, { name: "cherry" }]
|
|
118
|
-
expect(
|
|
119
|
-
findClosest(arr, "blueberry", { comparator: "lt", key: "name", threshold: "" })
|
|
120
|
-
).toEqual({
|
|
121
|
-
name: "banana",
|
|
122
|
-
})
|
|
123
|
-
expect(
|
|
124
|
-
findClosest(arr, "banana", { comparator: "lte", key: "name", threshold: "" })
|
|
125
|
-
).toEqual({
|
|
126
|
-
name: "banana",
|
|
127
|
-
})
|
|
128
|
-
expect(
|
|
129
|
-
findClosest(arr, "banana", { comparator: "gt", key: "name", threshold: "~" })
|
|
130
|
-
).toEqual({
|
|
131
|
-
name: "cherry",
|
|
132
|
-
})
|
|
133
|
-
expect(
|
|
134
|
-
findClosest(arr, "cherry", { comparator: "gte", key: "name", threshold: "~" })
|
|
135
|
-
).toEqual({
|
|
136
|
-
name: "cherry",
|
|
137
|
-
})
|
|
138
|
-
expect(
|
|
139
|
-
findClosest(arr, "aardvark", { comparator: "lt", key: "name", threshold: "" })
|
|
140
|
-
).toBeUndefined()
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it("returns undefined if no string matches threshold/key criteria", () => {
|
|
144
|
-
const arr = ["apple", "banana", "cherry"]
|
|
145
|
-
expect(findClosest(arr, "apple", { comparator: "lt", threshold: "" })).toBeUndefined()
|
|
146
|
-
expect(findClosest(arr, "cherry", { comparator: "gt" })).toBeUndefined()
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
it("can use abs comparator with string lengths", () => {
|
|
150
|
-
// This is a reasonable use-case for abs: find string with length closest to 4
|
|
151
|
-
const arr = ["a", "bb", "ccc", "dddd", "eeeee"]
|
|
152
|
-
// Map to string lengths using key
|
|
153
|
-
expect(findClosest(arr, 4, { comparator: "abs", key: "length" })).toEqual("dddd")
|
|
154
|
-
// If threshold is set so no string length is close enough
|
|
155
|
-
expect(
|
|
156
|
-
findClosest(arr, 4, { comparator: "abs", key: "length", threshold: -1 })
|
|
157
|
-
).toBeUndefined()
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it("uses transform as a function (same as map)", () => {
|
|
161
|
-
const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
|
|
162
|
-
const mapFn = (el) => el.v
|
|
163
|
-
expect(findClosest(arr, 6, { transform: mapFn })).toEqual({ v: 5 })
|
|
164
|
-
// Should take precedence over key if both are present
|
|
165
|
-
expect(findClosest(arr, 6, { key: "notUsed", transform: mapFn })).toEqual({ v: 5 })
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it("uses transform as a string (same as key)", () => {
|
|
169
|
-
const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
|
|
170
|
-
expect(findClosest(arr, 6, { transform: "v" })).toEqual({ v: 5 })
|
|
171
|
-
// Should take precedence over key if both are present
|
|
172
|
-
expect(findClosest(arr, 6, { key: "notUsed", transform: "v" })).toEqual({ v: 5 })
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
it("uses transform as a number (same as key)", () => {
|
|
176
|
-
const arr = [[1], [5], [10]]
|
|
177
|
-
expect(findClosest(arr, 6, { transform: 0 })).toEqual([5])
|
|
178
|
-
// Should take precedence over key if both are present
|
|
179
|
-
expect(findClosest(arr, 6, { key: "notUsed", transform: 0 })).toEqual([5])
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it("transform does not override key if key is already present and transform is not provided", () => {
|
|
183
|
-
const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
|
|
184
|
-
expect(findClosest(arr, 6, { key: "v" })).toEqual({ v: 5 })
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it("transform is ignored if not a function, string, or number", () => {
|
|
188
|
-
const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
|
|
189
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
190
|
-
expect(findClosest(arr, 6, { transform: null, key: "v" })).toEqual({ v: 5 })
|
|
191
|
-
expect(findClosest(arr, 6, { transform: {}, key: "v" })).toEqual({ v: 5 })
|
|
75
|
+
it("supports key as number", () => {
|
|
76
|
+
const arr = [[2], [8]]
|
|
77
|
+
expect(findClosestLT(arr, 7, { key: 0 })).toEqual([2])
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("respects cutoff", () => {
|
|
81
|
+
expect(findClosestLT([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
|
|
82
|
+
expect(findClosestLT([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
|
|
192
83
|
})
|
|
193
84
|
})
|
|
194
85
|
|
|
195
|
-
describe("
|
|
196
|
-
it("returns closest
|
|
197
|
-
expect(
|
|
198
|
-
expect(
|
|
199
|
-
expect(
|
|
86
|
+
describe("findClosestLTE", () => {
|
|
87
|
+
it("returns the closest element less than or equal to desired", () => {
|
|
88
|
+
expect(findClosestLTE([1, 5, 9], 5)).toBe(5)
|
|
89
|
+
expect(findClosestLTE([1, 5, 9], 6)).toBe(5)
|
|
90
|
+
expect(findClosestLTE([1, 5, 9], 1)).toBe(1)
|
|
91
|
+
expect(findClosestLTE([1, 5, 9], 0)).toBeUndefined()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("returns first match if tie", () => {
|
|
95
|
+
expect(findClosestLTE([2, 2, 1], 2)).toBe(2)
|
|
200
96
|
})
|
|
201
97
|
|
|
202
98
|
it("returns undefined for empty array", () => {
|
|
203
|
-
expect(
|
|
99
|
+
expect(findClosestLTE([], 10)).toBeUndefined()
|
|
204
100
|
})
|
|
205
101
|
|
|
206
|
-
it("
|
|
207
|
-
const arr = [{ v:
|
|
208
|
-
expect(
|
|
102
|
+
it("supports key as function", () => {
|
|
103
|
+
const arr = [{ v: 2 }, { v: 8 }]
|
|
104
|
+
expect(findClosestLTE(arr, 8, { key: (e) => e.v })).toEqual({ v: 8 })
|
|
209
105
|
})
|
|
210
106
|
|
|
211
|
-
it("
|
|
212
|
-
const arr = [{
|
|
213
|
-
expect(
|
|
107
|
+
it("supports key as string", () => {
|
|
108
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
109
|
+
expect(findClosestLTE(arr, 8, { key: "x" })).toEqual({ x: 1 })
|
|
214
110
|
})
|
|
215
111
|
|
|
216
|
-
it("
|
|
217
|
-
|
|
218
|
-
expect(
|
|
112
|
+
it("supports key as number", () => {
|
|
113
|
+
const arr = [[2], [8]]
|
|
114
|
+
expect(findClosestLTE(arr, 7, { key: 0 })).toEqual([2])
|
|
219
115
|
})
|
|
220
116
|
|
|
221
|
-
it("
|
|
222
|
-
expect(
|
|
223
|
-
|
|
224
|
-
expect(findClosestAbs(arr, 2, { key: "v" })).toEqual({ v: 1 })
|
|
225
|
-
expect(findClosestAbs(arr, 2, { map: (el) => el.v })).toEqual({ v: 1 })
|
|
117
|
+
it("respects cutoff", () => {
|
|
118
|
+
expect(findClosestLTE([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
|
|
119
|
+
expect(findClosestLTE([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
|
|
226
120
|
})
|
|
227
121
|
})
|
|
228
122
|
|
|
229
|
-
describe("
|
|
230
|
-
it("returns closest
|
|
231
|
-
expect(
|
|
232
|
-
expect(
|
|
123
|
+
describe("findClosestGT", () => {
|
|
124
|
+
it("returns the closest element greater than desired", () => {
|
|
125
|
+
expect(findClosestGT([1, 5, 9], 6)).toBe(9)
|
|
126
|
+
expect(findClosestGT([1, 5, 9], 0)).toBe(1)
|
|
127
|
+
expect(findClosestGT([1, 5, 9], 9)).toBeUndefined()
|
|
233
128
|
})
|
|
234
129
|
|
|
235
|
-
it("returns
|
|
236
|
-
|
|
237
|
-
expect(findClosestLT(arr, 6, { key: "v" })).toEqual({ v: 5 })
|
|
130
|
+
it("returns first match if tie", () => {
|
|
131
|
+
expect(findClosestGT([8, 8, 10], 7)).toBe(8)
|
|
238
132
|
})
|
|
239
133
|
|
|
240
|
-
it("returns
|
|
241
|
-
|
|
242
|
-
expect(findClosestLT(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
|
|
134
|
+
it("returns undefined for empty array", () => {
|
|
135
|
+
expect(findClosestGT([], 10)).toBeUndefined()
|
|
243
136
|
})
|
|
244
137
|
|
|
245
|
-
it("
|
|
246
|
-
|
|
247
|
-
expect(
|
|
138
|
+
it("supports key as function", () => {
|
|
139
|
+
const arr = [{ v: 2 }, { v: 8 }]
|
|
140
|
+
expect(findClosestGT(arr, 2, { key: (e) => e.v })).toEqual({ v: 8 })
|
|
248
141
|
})
|
|
249
142
|
|
|
250
|
-
it("
|
|
251
|
-
|
|
143
|
+
it("supports key as string", () => {
|
|
144
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
145
|
+
expect(findClosestGT(arr, 8, { key: "x" })).toEqual({ x: 10 })
|
|
252
146
|
})
|
|
253
|
-
})
|
|
254
147
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
expect(
|
|
258
|
-
expect(findClosestLTE([1, 3, 5, 7], 2)).toBe(1)
|
|
259
|
-
expect(findClosestLTE([1, 3, 5, 7], 0)).toBeUndefined()
|
|
148
|
+
it("supports key as number", () => {
|
|
149
|
+
const arr = [[2], [8]]
|
|
150
|
+
expect(findClosestGT(arr, 2, { key: 0 })).toEqual([8])
|
|
260
151
|
})
|
|
261
152
|
|
|
262
|
-
it("
|
|
263
|
-
|
|
264
|
-
expect(
|
|
153
|
+
it("respects cutoff", () => {
|
|
154
|
+
expect(findClosestGT([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
|
|
155
|
+
expect(findClosestGT([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
|
|
265
156
|
})
|
|
157
|
+
})
|
|
266
158
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
expect(
|
|
159
|
+
describe("findClosestGTE", () => {
|
|
160
|
+
it("returns the closest element greater than or equal to desired", () => {
|
|
161
|
+
expect(findClosestGTE([1, 5, 9], 5)).toBe(5)
|
|
162
|
+
expect(findClosestGTE([1, 5, 9], 4)).toBe(5)
|
|
163
|
+
expect(findClosestGTE([1, 5, 9], 10)).toBeUndefined()
|
|
270
164
|
})
|
|
271
165
|
|
|
272
|
-
it("
|
|
273
|
-
expect(
|
|
274
|
-
expect(findClosestLTE([1, 3, 5, 7], 6, { threshold: 5 })).toBeUndefined()
|
|
166
|
+
it("returns first match if tie", () => {
|
|
167
|
+
expect(findClosestGTE([8, 8, 10], 8)).toBe(8)
|
|
275
168
|
})
|
|
276
169
|
|
|
277
170
|
it("returns undefined for empty array", () => {
|
|
278
|
-
expect(
|
|
171
|
+
expect(findClosestGTE([], 10)).toBeUndefined()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it("supports key as function", () => {
|
|
175
|
+
const arr = [{ v: 2 }, { v: 8 }]
|
|
176
|
+
expect(findClosestGTE(arr, 2, { key: (e) => e.v })).toEqual({ v: 2 })
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("supports key as string", () => {
|
|
180
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
181
|
+
expect(findClosestGTE(arr, 8, { key: "x" })).toEqual({ x: 10 })
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("supports key as number", () => {
|
|
185
|
+
const arr = [[2], [8]]
|
|
186
|
+
expect(findClosestGTE(arr, 2, { key: 0 })).toEqual([2])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("respects cutoff", () => {
|
|
190
|
+
expect(findClosestGTE([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
|
|
191
|
+
expect(findClosestGTE([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
|
|
279
192
|
})
|
|
280
193
|
})
|
|
281
194
|
|
|
282
|
-
describe("
|
|
283
|
-
it("
|
|
284
|
-
expect(
|
|
285
|
-
expect(findClosestGT([1, 3, 5, 7], 7)).toBeUndefined()
|
|
195
|
+
describe("findClosest", () => {
|
|
196
|
+
it("defaults to abs comparator", () => {
|
|
197
|
+
expect(findClosest([1, 5, 9], 6)).toBe(5)
|
|
286
198
|
})
|
|
287
199
|
|
|
288
|
-
it("
|
|
289
|
-
|
|
290
|
-
expect(
|
|
200
|
+
it("calls correct comparator", () => {
|
|
201
|
+
expect(findClosest([1, 5, 9], 6, { comparator: "lt" })).toBe(5)
|
|
202
|
+
expect(findClosest([1, 5, 9], 6, { comparator: "lte" })).toBe(5)
|
|
203
|
+
expect(findClosest([1, 5, 9], 6, { comparator: "gt" })).toBe(9)
|
|
204
|
+
expect(findClosest([1, 5, 9], 6, { comparator: "gte" })).toBe(9)
|
|
205
|
+
expect(findClosest([1, 5, 9], 6, { comparator: "abs" })).toBe(5)
|
|
291
206
|
})
|
|
292
207
|
|
|
293
|
-
it("
|
|
294
|
-
|
|
295
|
-
|
|
208
|
+
it("throws on unknown comparator", () => {
|
|
209
|
+
expect(() => findClosest([1, 5, 9], 6, { comparator: "foo" })).toThrow(
|
|
210
|
+
"Unknown comparator: foo"
|
|
211
|
+
)
|
|
296
212
|
})
|
|
297
213
|
|
|
298
|
-
it("
|
|
299
|
-
|
|
300
|
-
expect(
|
|
214
|
+
it("passes options to underlying function", () => {
|
|
215
|
+
const arr = [{ x: 1 }, { x: 10 }]
|
|
216
|
+
expect(findClosest(arr, 8, { comparator: "abs", key: "x" })).toEqual({ x: 10 })
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
describe("findMin", () => {
|
|
221
|
+
it("returns the minimum value in a numeric array", () => {
|
|
222
|
+
expect(findMin([3, 1, 4, 2])).toBe(1)
|
|
301
223
|
})
|
|
302
224
|
|
|
303
225
|
it("returns undefined for empty array", () => {
|
|
304
|
-
expect(
|
|
226
|
+
expect(findMin([])).toBeUndefined()
|
|
305
227
|
})
|
|
306
|
-
})
|
|
307
228
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
expect(findClosestGTE([1, 3, 5, 7], 5)).toBe(5)
|
|
311
|
-
expect(findClosestGTE([1, 3, 5, 7], 6)).toBe(7)
|
|
312
|
-
expect(findClosestGTE([1, 3, 5, 7], 8)).toBeUndefined()
|
|
229
|
+
it("returns the first minimum if there are duplicates", () => {
|
|
230
|
+
expect(findMin([2, 1, 1, 3])).toBe(1)
|
|
313
231
|
})
|
|
314
232
|
|
|
315
|
-
it("
|
|
316
|
-
const arr = [{ v:
|
|
317
|
-
expect(
|
|
318
|
-
expect(findClosestGTE(arr, 10, { key: "v" })).toEqual({ v: 10 })
|
|
233
|
+
it("supports key as function", () => {
|
|
234
|
+
const arr = [{ v: 5 }, { v: 2 }, { v: 8 }]
|
|
235
|
+
expect(findMin(arr, { key: (e) => e.v })).toEqual({ v: 2 })
|
|
319
236
|
})
|
|
320
237
|
|
|
321
|
-
it("
|
|
322
|
-
const arr = [{
|
|
323
|
-
expect(
|
|
324
|
-
expect(findClosestGTE(arr, 10, { map: (el) => el.v })).toEqual({ v: 10 })
|
|
238
|
+
it("supports key as string", () => {
|
|
239
|
+
const arr = [{ x: 5 }, { x: 2 }, { x: 8 }]
|
|
240
|
+
expect(findMin(arr, { key: "x" })).toEqual({ x: 2 })
|
|
325
241
|
})
|
|
326
242
|
|
|
327
|
-
it("
|
|
328
|
-
|
|
329
|
-
expect(
|
|
243
|
+
it("supports key as number", () => {
|
|
244
|
+
const arr = [[5], [2], [8]]
|
|
245
|
+
expect(findMin(arr, { key: 0 })).toEqual([2])
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it("respects cutoff", () => {
|
|
249
|
+
expect(findMin([3, 1, 4, 2], { cutoff: 2 })).toBe(1)
|
|
250
|
+
expect(findMin([3, 1, 4, 2], { cutoff: 1 })).toBeUndefined()
|
|
251
|
+
})
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
describe("findMax", () => {
|
|
255
|
+
it("returns the maximum value in a numeric array", () => {
|
|
256
|
+
expect(findMax([3, 1, 4, 2])).toBe(4)
|
|
330
257
|
})
|
|
331
258
|
|
|
332
259
|
it("returns undefined for empty array", () => {
|
|
333
|
-
expect(
|
|
260
|
+
expect(findMax([])).toBeUndefined()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it("returns the first maximum if there are duplicates", () => {
|
|
264
|
+
expect(findMax([4, 2, 4, 1])).toBe(4)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it("supports key as function", () => {
|
|
268
|
+
const arr = [{ v: 5 }, { v: 2 }, { v: 8 }]
|
|
269
|
+
expect(findMax(arr, { key: (e) => e.v })).toEqual({ v: 8 })
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it("supports key as string", () => {
|
|
273
|
+
const arr = [{ x: 5 }, { x: 2 }, { x: 8 }]
|
|
274
|
+
expect(findMax(arr, { key: "x" })).toEqual({ x: 8 })
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it("supports key as number", () => {
|
|
278
|
+
const arr = [[5], [2], [8]]
|
|
279
|
+
expect(findMax(arr, { key: 0 })).toEqual([8])
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it("respects cutoff", () => {
|
|
283
|
+
expect(findMax([3, 1, 4, 2], { cutoff: 2 })).toBe(4)
|
|
284
|
+
expect(findMax([3, 1, 4, 2], { cutoff: 4 })).toBeUndefined()
|
|
334
285
|
})
|
|
335
286
|
})
|