@tim-code/my-util 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/find.js +59 -56
  3. package/src/find.test.js +135 -220
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
package/src/find.js CHANGED
@@ -1,150 +1,150 @@
1
- export function findClosestAbs(array, desired, { key, map, threshold = Infinity } = {}) {
1
+ export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {}) {
2
2
  let closest
3
- if (map) {
3
+ if (typeof key === "function") {
4
4
  for (let i = 0; i < array.length; i++) {
5
5
  const element = array[i]
6
- const value = map(element, i, array)
6
+ const value = key(element, i, array)
7
7
  const diff = Math.abs(value - desired)
8
- if (diff < threshold) {
8
+ if (diff < cutoff) {
9
9
  closest = element
10
- threshold = diff
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 < threshold) {
17
+ if (diff < cutoff) {
18
18
  closest = element
19
- threshold = diff
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 < threshold) {
25
+ if (diff < cutoff) {
26
26
  closest = value
27
- threshold = diff
27
+ cutoff = diff
28
28
  }
29
29
  }
30
30
  }
31
31
  return closest
32
32
  }
33
33
 
34
- export function findClosestLT(array, desired, { key, map, threshold = -Infinity } = {}) {
34
+ export function findClosestLT(array, desired, { key, cutoff = -Infinity } = {}) {
35
35
  let closest
36
- if (map) {
36
+ if (typeof key === "function") {
37
37
  for (let i = 0; i < array.length; i++) {
38
38
  const element = array[i]
39
- const value = map(element, i, array)
40
- if (value < desired && value > threshold) {
39
+ const value = key(element, i, array)
40
+ if (value < desired && value > cutoff) {
41
41
  closest = element
42
- threshold = value
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 > threshold) {
48
+ if (value < desired && value > cutoff) {
49
49
  closest = element
50
- threshold = value
50
+ cutoff = value
51
51
  }
52
52
  }
53
53
  } else {
54
54
  for (const value of array) {
55
- if (value < desired && value > threshold) {
55
+ if (value < desired && value > cutoff) {
56
56
  closest = value
57
- threshold = value
57
+ cutoff = value
58
58
  }
59
59
  }
60
60
  }
61
61
  return closest
62
62
  }
63
63
 
64
- export function findClosestLTE(array, desired, { key, map, threshold = -Infinity } = {}) {
64
+ export function findClosestLTE(array, desired, { key, cutoff = -Infinity } = {}) {
65
65
  let closest
66
- if (map) {
66
+ if (typeof key === "function") {
67
67
  for (let i = 0; i < array.length; i++) {
68
68
  const element = array[i]
69
- const value = map(element, i, array)
70
- if (value <= desired && value > threshold) {
69
+ const value = key(element, i, array)
70
+ if (value <= desired && value > cutoff) {
71
71
  closest = element
72
- threshold = value
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 > threshold) {
78
+ if (value <= desired && value > cutoff) {
79
79
  closest = element
80
- threshold = value
80
+ cutoff = value
81
81
  }
82
82
  }
83
83
  } else {
84
84
  for (const value of array) {
85
- if (value <= desired && value > threshold) {
85
+ if (value <= desired && value > cutoff) {
86
86
  closest = value
87
- threshold = value
87
+ cutoff = value
88
88
  }
89
89
  }
90
90
  }
91
91
  return closest
92
92
  }
93
93
 
94
- export function findClosestGT(array, desired, { key, map, threshold = Infinity } = {}) {
94
+ export function findClosestGT(array, desired, { key, cutoff = Infinity } = {}) {
95
95
  let closest
96
- if (map) {
96
+ if (typeof key === "function") {
97
97
  for (let i = 0; i < array.length; i++) {
98
98
  const element = array[i]
99
- const value = map(element, i, array)
100
- if (value > desired && value < threshold) {
99
+ const value = key(element, i, array)
100
+ if (value > desired && value < cutoff) {
101
101
  closest = element
102
- threshold = value
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 < threshold) {
108
+ if (value > desired && value < cutoff) {
109
109
  closest = element
110
- threshold = value
110
+ cutoff = value
111
111
  }
112
112
  }
113
113
  } else {
114
114
  for (const value of array) {
115
- if (value > desired && value < threshold) {
115
+ if (value > desired && value < cutoff) {
116
116
  closest = value
117
- threshold = value
117
+ cutoff = value
118
118
  }
119
119
  }
120
120
  }
121
121
  return closest
122
122
  }
123
123
 
124
- export function findClosestGTE(array, desired, { key, map, threshold = Infinity } = {}) {
124
+ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {}) {
125
125
  let closest
126
- if (map) {
126
+ if (typeof key === "function") {
127
127
  for (let i = 0; i < array.length; i++) {
128
128
  const element = array[i]
129
- const value = map(element, i, array)
130
- if (value >= desired && value < threshold) {
129
+ const value = key(element, i, array)
130
+ if (value >= desired && value < cutoff) {
131
131
  closest = element
132
- threshold = value
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 < threshold) {
138
+ if (value >= desired && value < cutoff) {
139
139
  closest = element
140
- threshold = value
140
+ cutoff = value
141
141
  }
142
142
  }
143
143
  } else {
144
144
  for (const value of array) {
145
- if (value >= desired && value < threshold) {
145
+ if (value >= desired && value < cutoff) {
146
146
  closest = value
147
- threshold = value
147
+ cutoff = value
148
148
  }
149
149
  }
150
150
  }
@@ -152,17 +152,20 @@ 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 "threshold" and "comparator".
157
- * "~" and "" are good threshold string values for gt/gte and lt/lte respectively.
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=} options.key If specified, will consider the value for each element's key instead of the element itself.
163
- * @param {Function=} options.map If specified, will compute value by calling provided function on the element. Takes precedence over key.
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.
164
165
  * @param {string=} options.comparator "abs", "lt", "lte", "gt", "gte", "abs". Default is "abs" which implies T is number.
165
- * @param {V=} options.threshold If specified, uses a different initial min/max/difference than positive or negative infinity.
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.
166
169
  * @returns {T|undefined}
167
170
  */
168
171
  export function findClosest(array, value, options = {}) {
package/src/find.test.js CHANGED
@@ -1,301 +1,216 @@
1
- import {
2
- findClosest,
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
- } from "./find.js"
7
+ findClosestGT,
8
+ findClosestGTE,
9
+ findClosest,
10
+ } = await import("./find.js")
9
11
 
10
- describe("findClosest", () => {
11
- it("returns the closest value by absolute difference (default comparator)", () => {
12
- expect(findClosest([1, 5, 10], 6)).toBe(5)
13
- expect(findClosest([1, 5, 10], 8)).toBe(10)
14
- expect(findClosest([1, 5, 10], 1)).toBe(1)
12
+ describe("findClosestAbs", () => {
13
+ it("returns the element closest in absolute value to desired", () => {
14
+ expect(findClosestAbs([1, 5, 9], 6)).toBe(5)
15
+ expect(findClosestAbs([1, 5, 9], 8)).toBe(9)
16
+ expect(findClosestAbs([1, 5, 9], 1)).toBe(1)
15
17
  })
16
18
 
17
- it("returns undefined if array is empty", () => {
18
- expect(findClosest([], 10)).toBeUndefined()
19
+ it("returns the first element in case of tie", () => {
20
+ expect(findClosestAbs([4, 8], 6)).toBe(4)
19
21
  })
20
22
 
21
- it("returns the closest object by key (abs)", () => {
22
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
23
- expect(findClosest(arr, 6, { key: "v" })).toEqual({ v: 5 })
23
+ it("returns undefined for empty array", () => {
24
+ expect(findClosestAbs([], 10)).toBeUndefined()
24
25
  })
25
26
 
26
- it("returns the closest value less than input (lt comparator)", () => {
27
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt" })).toBe(5)
28
- expect(findClosest([1, 3, 5, 7], 1, { comparator: "lt" })).toBeUndefined()
27
+ it("supports key as function", () => {
28
+ const arr = [{ v: 2 }, { v: 8 }]
29
+ expect(findClosestAbs(arr, 5, { key: (e) => e.v })).toEqual({ v: 2 })
29
30
  })
30
31
 
31
- it("returns the closest value less than or equal to input (lte comparator)", () => {
32
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "lte" })).toBe(5)
33
- expect(findClosest([1, 3, 5, 7], 2, { comparator: "lte" })).toBe(1)
34
- expect(findClosest([1, 3, 5, 7], 0, { comparator: "lte" })).toBeUndefined()
32
+ it("supports key as string", () => {
33
+ const arr = [{ x: 1 }, { x: 10 }]
34
+ expect(findClosestAbs(arr, 8, { key: "x" })).toEqual({ x: 10 })
35
35
  })
36
36
 
37
- it("returns the closest value greater than input (gt comparator)", () => {
38
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "gt" })).toBe(7)
39
- expect(findClosest([1, 3, 5, 7], 7, { comparator: "gt" })).toBeUndefined()
37
+ it("supports key as number", () => {
38
+ const arr = [[2], [8]]
39
+ expect(findClosestAbs(arr, 7, { key: 0 })).toEqual([8])
40
40
  })
41
41
 
42
- it("returns the closest value greater than or equal to input (gte comparator)", () => {
43
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "gte" })).toBe(5)
44
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "gte" })).toBe(7)
45
- expect(findClosest([1, 3, 5, 7], 8, { comparator: "gte" })).toBeUndefined()
42
+ it("respects cutoff", () => {
43
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 2 })).toBe(5)
44
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 1 })).toBeUndefined()
46
45
  })
46
+ })
47
47
 
48
- it("returns the closest object by key for lt/lte/gt/gte", () => {
49
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
50
- expect(findClosest(arr, 6, { comparator: "lt", key: "v" })).toEqual({ v: 5 })
51
- expect(findClosest(arr, 6, { comparator: "lte", key: "v" })).toEqual({ v: 5 })
52
- expect(findClosest(arr, 6, { comparator: "gt", key: "v" })).toEqual({ v: 10 })
53
- expect(findClosest(arr, 10, { comparator: "gte", key: "v" })).toEqual({ v: 10 })
48
+ describe("findClosestLT", () => {
49
+ it("returns the closest element less than desired", () => {
50
+ expect(findClosestLT([1, 5, 9], 6)).toBe(5)
51
+ expect(findClosestLT([1, 5, 9], 2)).toBe(1)
52
+ expect(findClosestLT([1, 5, 9], 1)).toBeUndefined()
54
53
  })
55
54
 
56
- it("respects the threshold option for abs comparator", () => {
57
- expect(findClosest([1, 5, 10], 6, { threshold: 0.5 })).toBeUndefined()
58
- expect(findClosest([1, 5, 10], 6, { threshold: 2 })).toBe(5)
55
+ it("returns first match if tie", () => {
56
+ expect(findClosestLT([2, 2, 1], 3)).toBe(2)
59
57
  })
60
58
 
61
- it("respects the threshold option for lt/lte/gt/gte", () => {
62
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt", threshold: 4 })).toBe(5)
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)
59
+ it("returns undefined for empty array", () => {
60
+ expect(findClosestLT([], 10)).toBeUndefined()
66
61
  })
67
62
 
68
- it("throws for unknown comparator", () => {
69
- expect(() => findClosest([1, 2, 3], 2, { comparator: "foo" })).toThrow(
70
- "Unknown comparator: foo"
71
- )
63
+ it("supports key as function", () => {
64
+ const arr = [{ v: 2 }, { v: 8 }]
65
+ expect(findClosestLT(arr, 8, { key: (e) => e.v })).toEqual({ v: 2 })
72
66
  })
73
67
 
74
- it("returns undefined if no element matches threshold/key criteria", () => {
75
- expect(
76
- findClosest([{ v: 1 }], 10, { comparator: "gt", key: "v", threshold: 1 })
77
- ).toBeUndefined()
78
- expect(
79
- findClosest([{ v: 1 }], 0, { comparator: "lt", key: "v", threshold: 1 })
80
- ).toBeUndefined()
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()
68
+ it("supports key as string", () => {
69
+ const arr = [{ x: 1 }, { x: 10 }]
70
+ expect(findClosestLT(arr, 8, { key: "x" })).toEqual({ x: 1 })
158
71
  })
159
- })
160
72
 
161
- describe("findClosestAbs", () => {
162
- it("returns closest value by absolute difference", () => {
163
- expect(findClosestAbs([1, 5, 10], 6)).toBe(5)
164
- expect(findClosestAbs([1, 5, 10], 8)).toBe(10)
165
- expect(findClosestAbs([1, 5, 10], 1)).toBe(1)
73
+ it("supports key as number", () => {
74
+ const arr = [[2], [8]]
75
+ expect(findClosestLT(arr, 7, { key: 0 })).toEqual([2])
166
76
  })
167
77
 
168
- it("returns undefined for empty array", () => {
169
- expect(findClosestAbs([], 10)).toBeUndefined()
78
+ it("respects cutoff", () => {
79
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
80
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
170
81
  })
82
+ })
171
83
 
172
- it("returns closest object by key", () => {
173
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
174
- expect(findClosestAbs(arr, 6, { key: "v" })).toEqual({ v: 5 })
84
+ describe("findClosestLTE", () => {
85
+ it("returns the closest element less than or equal to desired", () => {
86
+ expect(findClosestLTE([1, 5, 9], 5)).toBe(5)
87
+ expect(findClosestLTE([1, 5, 9], 6)).toBe(5)
88
+ expect(findClosestLTE([1, 5, 9], 1)).toBe(1)
89
+ expect(findClosestLTE([1, 5, 9], 0)).toBeUndefined()
175
90
  })
176
91
 
177
- it("returns closest value by map", () => {
178
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
179
- expect(findClosestAbs(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
92
+ it("returns first match if tie", () => {
93
+ expect(findClosestLTE([2, 2, 1], 2)).toBe(2)
180
94
  })
181
95
 
182
- it("respects threshold", () => {
183
- expect(findClosestAbs([1, 5, 10], 6, { threshold: 0.5 })).toBeUndefined()
184
- expect(findClosestAbs([1, 5, 10], 6, { threshold: 2 })).toBe(5)
96
+ it("returns undefined for empty array", () => {
97
+ expect(findClosestLTE([], 10)).toBeUndefined()
185
98
  })
186
99
 
187
- it("skips NaN in value mode but not in key/map mode", () => {
188
- expect(findClosestAbs([1, NaN, 5], 4)).toBe(5)
189
- const arr = [{ v: 1 }, { v: NaN }, { v: 5 }]
190
- expect(findClosestAbs(arr, 2, { key: "v" })).toEqual({ v: 1 })
191
- expect(findClosestAbs(arr, 2, { map: (el) => el.v })).toEqual({ v: 1 })
100
+ it("supports key as function", () => {
101
+ const arr = [{ v: 2 }, { v: 8 }]
102
+ expect(findClosestLTE(arr, 8, { key: (e) => e.v })).toEqual({ v: 8 })
192
103
  })
193
- })
194
104
 
195
- describe("findClosestLT", () => {
196
- it("returns closest value less than desired", () => {
197
- expect(findClosestLT([1, 3, 5, 7], 6)).toBe(5)
198
- expect(findClosestLT([1, 3, 5, 7], 1)).toBeUndefined()
105
+ it("supports key as string", () => {
106
+ const arr = [{ x: 1 }, { x: 10 }]
107
+ expect(findClosestLTE(arr, 8, { key: "x" })).toEqual({ x: 1 })
199
108
  })
200
109
 
201
- it("returns closest object by key", () => {
202
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
203
- expect(findClosestLT(arr, 6, { key: "v" })).toEqual({ v: 5 })
110
+ it("supports key as number", () => {
111
+ const arr = [[2], [8]]
112
+ expect(findClosestLTE(arr, 7, { key: 0 })).toEqual([2])
204
113
  })
205
114
 
206
- it("returns closest object by map", () => {
207
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
208
- expect(findClosestLT(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
115
+ it("respects cutoff", () => {
116
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
117
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
209
118
  })
119
+ })
210
120
 
211
- it("respects threshold", () => {
212
- expect(findClosestLT([1, 3, 5, 7], 6, { threshold: 4 })).toBe(5)
213
- expect(findClosestLT([1, 3, 5, 7], 6, { threshold: 5 })).toBeUndefined()
121
+ describe("findClosestGT", () => {
122
+ it("returns the closest element greater than desired", () => {
123
+ expect(findClosestGT([1, 5, 9], 6)).toBe(9)
124
+ expect(findClosestGT([1, 5, 9], 0)).toBe(1)
125
+ expect(findClosestGT([1, 5, 9], 9)).toBeUndefined()
214
126
  })
215
127
 
216
- it("returns undefined for empty array", () => {
217
- expect(findClosestLT([], 10)).toBeUndefined()
128
+ it("returns first match if tie", () => {
129
+ expect(findClosestGT([8, 8, 10], 7)).toBe(8)
218
130
  })
219
- })
220
131
 
221
- describe("findClosestLTE", () => {
222
- it("returns closest value less than or equal to desired", () => {
223
- expect(findClosestLTE([1, 3, 5, 7], 5)).toBe(5)
224
- expect(findClosestLTE([1, 3, 5, 7], 2)).toBe(1)
225
- expect(findClosestLTE([1, 3, 5, 7], 0)).toBeUndefined()
132
+ it("returns undefined for empty array", () => {
133
+ expect(findClosestGT([], 10)).toBeUndefined()
226
134
  })
227
135
 
228
- it("returns closest object by key", () => {
229
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
230
- expect(findClosestLTE(arr, 6, { key: "v" })).toEqual({ v: 5 })
136
+ it("supports key as function", () => {
137
+ const arr = [{ v: 2 }, { v: 8 }]
138
+ expect(findClosestGT(arr, 2, { key: (e) => e.v })).toEqual({ v: 8 })
231
139
  })
232
140
 
233
- it("returns closest object by map", () => {
234
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
235
- expect(findClosestLTE(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
141
+ it("supports key as string", () => {
142
+ const arr = [{ x: 1 }, { x: 10 }]
143
+ expect(findClosestGT(arr, 8, { key: "x" })).toEqual({ x: 10 })
236
144
  })
237
145
 
238
- it("respects threshold", () => {
239
- expect(findClosestLTE([1, 3, 5, 7], 6, { threshold: 4 })).toBe(5)
240
- expect(findClosestLTE([1, 3, 5, 7], 6, { threshold: 5 })).toBeUndefined()
146
+ it("supports key as number", () => {
147
+ const arr = [[2], [8]]
148
+ expect(findClosestGT(arr, 2, { key: 0 })).toEqual([8])
241
149
  })
242
150
 
243
- it("returns undefined for empty array", () => {
244
- expect(findClosestLTE([], 10)).toBeUndefined()
151
+ it("respects cutoff", () => {
152
+ expect(findClosestGT([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
153
+ expect(findClosestGT([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
245
154
  })
246
155
  })
247
156
 
248
- describe("findClosestGT", () => {
249
- it("returns closest value greater than desired", () => {
250
- expect(findClosestGT([1, 3, 5, 7], 5)).toBe(7)
251
- expect(findClosestGT([1, 3, 5, 7], 7)).toBeUndefined()
157
+ describe("findClosestGTE", () => {
158
+ it("returns the closest element greater than or equal to desired", () => {
159
+ expect(findClosestGTE([1, 5, 9], 5)).toBe(5)
160
+ expect(findClosestGTE([1, 5, 9], 4)).toBe(5)
161
+ expect(findClosestGTE([1, 5, 9], 10)).toBeUndefined()
162
+ })
163
+
164
+ it("returns first match if tie", () => {
165
+ expect(findClosestGTE([8, 8, 10], 8)).toBe(8)
252
166
  })
253
167
 
254
- it("returns closest object by key", () => {
255
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
256
- expect(findClosestGT(arr, 6, { key: "v" })).toEqual({ v: 10 })
168
+ it("returns undefined for empty array", () => {
169
+ expect(findClosestGTE([], 10)).toBeUndefined()
257
170
  })
258
171
 
259
- it("returns closest object by map", () => {
260
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
261
- expect(findClosestGT(arr, 6, { map: (el) => el.v })).toEqual({ v: 10 })
172
+ it("supports key as function", () => {
173
+ const arr = [{ v: 2 }, { v: 8 }]
174
+ expect(findClosestGTE(arr, 2, { key: (e) => e.v })).toEqual({ v: 2 })
262
175
  })
263
176
 
264
- it("respects threshold", () => {
265
- expect(findClosestGT([1, 3, 5, 7], 6, { threshold: 7 })).toBeUndefined()
266
- expect(findClosestGT([1, 3, 5, 7], 6, { threshold: 10 })).toBe(7)
177
+ it("supports key as string", () => {
178
+ const arr = [{ x: 1 }, { x: 10 }]
179
+ expect(findClosestGTE(arr, 8, { key: "x" })).toEqual({ x: 10 })
267
180
  })
268
181
 
269
- it("returns undefined for empty array", () => {
270
- expect(findClosestGT([], 10)).toBeUndefined()
182
+ it("supports key as number", () => {
183
+ const arr = [[2], [8]]
184
+ expect(findClosestGTE(arr, 2, { key: 0 })).toEqual([2])
271
185
  })
272
- })
273
186
 
274
- describe("findClosestGTE", () => {
275
- it("returns closest value greater than or equal to desired", () => {
276
- expect(findClosestGTE([1, 3, 5, 7], 5)).toBe(5)
277
- expect(findClosestGTE([1, 3, 5, 7], 6)).toBe(7)
278
- expect(findClosestGTE([1, 3, 5, 7], 8)).toBeUndefined()
187
+ it("respects cutoff", () => {
188
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
189
+ expect(findClosestGTE([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
279
190
  })
191
+ })
280
192
 
281
- it("returns closest object by key", () => {
282
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
283
- expect(findClosestGTE(arr, 6, { key: "v" })).toEqual({ v: 10 })
284
- expect(findClosestGTE(arr, 10, { key: "v" })).toEqual({ v: 10 })
193
+ describe("findClosest", () => {
194
+ it("defaults to abs comparator", () => {
195
+ expect(findClosest([1, 5, 9], 6)).toBe(5)
285
196
  })
286
197
 
287
- it("returns closest object by map", () => {
288
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
289
- expect(findClosestGTE(arr, 6, { map: (el) => el.v })).toEqual({ v: 10 })
290
- expect(findClosestGTE(arr, 10, { map: (el) => el.v })).toEqual({ v: 10 })
198
+ it("calls correct comparator", () => {
199
+ expect(findClosest([1, 5, 9], 6, { comparator: "lt" })).toBe(5)
200
+ expect(findClosest([1, 5, 9], 6, { comparator: "lte" })).toBe(5)
201
+ expect(findClosest([1, 5, 9], 6, { comparator: "gt" })).toBe(9)
202
+ expect(findClosest([1, 5, 9], 6, { comparator: "gte" })).toBe(9)
203
+ expect(findClosest([1, 5, 9], 6, { comparator: "abs" })).toBe(5)
291
204
  })
292
205
 
293
- it("respects threshold", () => {
294
- expect(findClosestGTE([1, 3, 5, 7], 6, { threshold: 7 })).toBeUndefined()
295
- expect(findClosestGTE([1, 3, 5, 7], 6, { threshold: 10 })).toBe(7)
206
+ it("throws on unknown comparator", () => {
207
+ expect(() => findClosest([1, 5, 9], 6, { comparator: "foo" })).toThrow(
208
+ "Unknown comparator: foo"
209
+ )
296
210
  })
297
211
 
298
- it("returns undefined for empty array", () => {
299
- expect(findClosestGTE([], 10)).toBeUndefined()
212
+ it("passes options to underlying function", () => {
213
+ const arr = [{ x: 1 }, { x: 10 }]
214
+ expect(findClosest(arr, 8, { comparator: "abs", key: "x" })).toEqual({ x: 10 })
300
215
  })
301
216
  })