@tim-code/my-util 0.2.0 → 0.2.2
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 +83 -1
- package/src/find.test.js +71 -1
- package/src/math.js +79 -0
- package/src/math.test.js +97 -1
package/package.json
CHANGED
package/src/find.js
CHANGED
|
@@ -182,6 +182,88 @@ export function findClosest(array, value, options = {}) {
|
|
|
182
182
|
case "abs":
|
|
183
183
|
return findClosestAbs(array, value, options)
|
|
184
184
|
default:
|
|
185
|
-
throw new Error(`
|
|
185
|
+
throw new Error(`unknown comparator: ${comparator}`)
|
|
186
186
|
}
|
|
187
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
|
@@ -7,6 +7,8 @@ const {
|
|
|
7
7
|
findClosestGT,
|
|
8
8
|
findClosestGTE,
|
|
9
9
|
findClosest,
|
|
10
|
+
findMin,
|
|
11
|
+
findMax,
|
|
10
12
|
} = await import("./find.js")
|
|
11
13
|
|
|
12
14
|
describe("findClosestAbs", () => {
|
|
@@ -205,7 +207,7 @@ describe("findClosest", () => {
|
|
|
205
207
|
|
|
206
208
|
it("throws on unknown comparator", () => {
|
|
207
209
|
expect(() => findClosest([1, 5, 9], 6, { comparator: "foo" })).toThrow(
|
|
208
|
-
"
|
|
210
|
+
"unknown comparator: foo"
|
|
209
211
|
)
|
|
210
212
|
})
|
|
211
213
|
|
|
@@ -214,3 +216,71 @@ describe("findClosest", () => {
|
|
|
214
216
|
expect(findClosest(arr, 8, { comparator: "abs", key: "x" })).toEqual({ x: 10 })
|
|
215
217
|
})
|
|
216
218
|
})
|
|
219
|
+
|
|
220
|
+
describe("findMin", () => {
|
|
221
|
+
it("returns the minimum value in a numeric array", () => {
|
|
222
|
+
expect(findMin([3, 1, 4, 2])).toBe(1)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it("returns undefined for empty array", () => {
|
|
226
|
+
expect(findMin([])).toBeUndefined()
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it("returns the first minimum if there are duplicates", () => {
|
|
230
|
+
expect(findMin([2, 1, 1, 3])).toBe(1)
|
|
231
|
+
})
|
|
232
|
+
|
|
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 })
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it("supports key as string", () => {
|
|
239
|
+
const arr = [{ x: 5 }, { x: 2 }, { x: 8 }]
|
|
240
|
+
expect(findMin(arr, { key: "x" })).toEqual({ x: 2 })
|
|
241
|
+
})
|
|
242
|
+
|
|
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)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it("returns undefined for empty array", () => {
|
|
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()
|
|
285
|
+
})
|
|
286
|
+
})
|
package/src/math.js
CHANGED
|
@@ -23,6 +23,85 @@ export function line([x1, y1], [x2, y2]) {
|
|
|
23
23
|
return (x) => m * x + b
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Calculate the sum of values in an array.
|
|
28
|
+
* @template T
|
|
29
|
+
* @param {Array<T>} array
|
|
30
|
+
* @param {Object} [options]
|
|
31
|
+
* @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
export function sum(array, { key } = {}) {
|
|
35
|
+
let total = 0
|
|
36
|
+
if (typeof key === "function") {
|
|
37
|
+
for (let i = 0; i < array.length; i++) {
|
|
38
|
+
total += key(array[i], i, array)
|
|
39
|
+
}
|
|
40
|
+
} else if (typeof key === "string" || typeof key === "number") {
|
|
41
|
+
for (let i = 0; i < array.length; i++) {
|
|
42
|
+
total += array[i][key]
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
for (let i = 0; i < array.length; i++) {
|
|
46
|
+
total += array[i]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return total
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Calculate the average (mean) of values in an array.
|
|
54
|
+
* @template T
|
|
55
|
+
* @param {Array<T>} array
|
|
56
|
+
* @param {Object} [options]
|
|
57
|
+
* @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
|
|
58
|
+
* @returns {number}
|
|
59
|
+
* @throws {Error} If the array is empty.
|
|
60
|
+
*/
|
|
61
|
+
export function average(array, { key } = {}) {
|
|
62
|
+
if (array.length === 0) {
|
|
63
|
+
throw new Error("cannot compute average of empty array")
|
|
64
|
+
}
|
|
65
|
+
return sum(array, { key }) / array.length
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate the variance of values in an array.
|
|
70
|
+
* @template T
|
|
71
|
+
* @param {Array<T>} array
|
|
72
|
+
* @param {Object} [options]
|
|
73
|
+
* @param {string|number|((element: T, index: number, array: Array<T>) => number)=} options.key
|
|
74
|
+
* @returns {number}
|
|
75
|
+
* @throws {Error} If the array is empty.
|
|
76
|
+
*/
|
|
77
|
+
export function variance(array, { key } = {}) {
|
|
78
|
+
if (array.length === 0) {
|
|
79
|
+
throw new Error("cannot compute variance of empty array")
|
|
80
|
+
}
|
|
81
|
+
const avg = average(array, { key })
|
|
82
|
+
let total = 0
|
|
83
|
+
if (typeof key === "function") {
|
|
84
|
+
for (let i = 0; i < array.length; i++) {
|
|
85
|
+
const value = key(array[i], i, array)
|
|
86
|
+
const diff = value - avg
|
|
87
|
+
total += diff * diff
|
|
88
|
+
}
|
|
89
|
+
} else if (typeof key === "string" || typeof key === "number") {
|
|
90
|
+
for (let i = 0; i < array.length; i++) {
|
|
91
|
+
const value = array[i][key]
|
|
92
|
+
const diff = value - avg
|
|
93
|
+
total += diff * diff
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
for (let i = 0; i < array.length; i++) {
|
|
97
|
+
const value = array[i]
|
|
98
|
+
const diff = value - avg
|
|
99
|
+
total += diff * diff
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return total / array.length
|
|
103
|
+
}
|
|
104
|
+
|
|
26
105
|
/**
|
|
27
106
|
* Prepend a plus to a number or string if positive.
|
|
28
107
|
* @param {number|string} number Or string
|
package/src/math.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "@jest/globals"
|
|
2
|
-
const { mod, formatPlus, line } = await import("./math.js")
|
|
2
|
+
const { mod, formatPlus, line, sum, average, variance } = await import("./math.js")
|
|
3
3
|
|
|
4
4
|
describe("mod", () => {
|
|
5
5
|
it("returns n when n is less than m and n is non-negative", () => {
|
|
@@ -104,6 +104,102 @@ describe("line", () => {
|
|
|
104
104
|
})
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
+
describe("sum", () => {
|
|
108
|
+
it("returns 0 for an empty array", () => {
|
|
109
|
+
expect(sum([])).toBe(0)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("sums a simple array of numbers", () => {
|
|
113
|
+
expect(sum([1, 2, 3])).toBe(6)
|
|
114
|
+
expect(sum([-1, 1, 2])).toBe(2)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it("sums using a key function", () => {
|
|
118
|
+
const arr = [{ v: 2 }, { v: 3 }, { v: 4 }]
|
|
119
|
+
expect(sum(arr, { key: (el) => el.v })).toBe(9)
|
|
120
|
+
expect(sum(arr, { key: (el, i) => el.v * i })).toBe(0 * 2 + 1 * 3 + 2 * 4) // 0 + 3 + 8 = 11
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("sums using a string key", () => {
|
|
124
|
+
const arr = [{ v: 2 }, { v: 3 }, { v: 4 }]
|
|
125
|
+
expect(sum(arr, { key: "v" })).toBe(9)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it("sums using a numeric key", () => {
|
|
129
|
+
const arr = [[1], [2], [3]]
|
|
130
|
+
expect(sum(arr, { key: 0 })).toBe(6)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("handles array of numbers with undefined options", () => {
|
|
134
|
+
expect(sum([5, 6, 7])).toBe(18)
|
|
135
|
+
expect(sum([5, 6, 7], undefined)).toBe(18)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it("returns NaN if key is string/number and property is missing", () => {
|
|
139
|
+
expect(sum([{ a: 1 }, {}], { key: "a" })).toBeNaN()
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe("average", () => {
|
|
144
|
+
it("computes the mean of a number array", () => {
|
|
145
|
+
expect(average([1, 2, 3])).toBe(2)
|
|
146
|
+
expect(average([1, 1, 1, 1])).toBe(1)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("computes the mean using a key function", () => {
|
|
150
|
+
const arr = [{ v: 2 }, { v: 4 }]
|
|
151
|
+
expect(average(arr, { key: (el) => el.v })).toBe(3)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it("computes the mean using a string key", () => {
|
|
155
|
+
const arr = [{ v: 2 }, { v: 4 }]
|
|
156
|
+
expect(average(arr, { key: "v" })).toBe(3)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("computes the mean using a numeric key", () => {
|
|
160
|
+
const arr = [[2], [4]]
|
|
161
|
+
expect(average(arr, { key: 0 })).toBe(3)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it("throws for empty array", () => {
|
|
165
|
+
expect(() => average([])).toThrow("cannot compute average of empty array")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it("returns NaN if key is string/number and property is missing", () => {
|
|
169
|
+
expect(average([{ a: 1 }, {}], { key: "a" })).toBeNaN()
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
describe("variance", () => {
|
|
174
|
+
it("computes the variance of a number array", () => {
|
|
175
|
+
expect(variance([1, 2, 3])).toBeCloseTo(2 / 3)
|
|
176
|
+
expect(variance([1, 1, 1, 1])).toBe(0)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it("computes the variance using a key function", () => {
|
|
180
|
+
const arr = [{ v: 2 }, { v: 4 }]
|
|
181
|
+
expect(variance(arr, { key: (el) => el.v })).toBe(1)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("computes the variance using a string key", () => {
|
|
185
|
+
const arr = [{ v: 2 }, { v: 4 }]
|
|
186
|
+
expect(variance(arr, { key: "v" })).toBe(1)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it("computes the variance using a numeric key", () => {
|
|
190
|
+
const arr = [[2], [4]]
|
|
191
|
+
expect(variance(arr, { key: 0 })).toBe(1)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("throws for empty array", () => {
|
|
195
|
+
expect(() => variance([])).toThrow("cannot compute variance of empty array")
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it("returns NaN if key is string/number and property is missing", () => {
|
|
199
|
+
expect(variance([{ a: 1 }, {}], { key: "a" })).toBeNaN()
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
107
203
|
describe("formatPlus", () => {
|
|
108
204
|
it("prepends a plus for positive numbers", () => {
|
|
109
205
|
expect(formatPlus(1)).toBe("+1")
|