@tim-code/my-util 0.6.3 → 0.6.5
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 +2 -2
- package/src/array.js +31 -10
- package/src/array.test.js +32 -1
- package/src/math.js +1 -1
- package/src/object.js +1 -1
- package/src/run.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tim-code/my-util",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tim Sprowl",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@jest/globals": "^30.2.0",
|
|
26
|
-
"@tim-code/eslint-config": "^2.0
|
|
26
|
+
"@tim-code/eslint-config": "^2.1.0",
|
|
27
27
|
"jest": "^30.2.0"
|
|
28
28
|
},
|
|
29
29
|
"jest": {
|
package/src/array.js
CHANGED
|
@@ -176,9 +176,30 @@ export function descending(transform) {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
function compareUndefinedNullEmpty(a, b) {
|
|
180
|
+
if (b === undefined || b === null || b === "") {
|
|
181
|
+
if (a === undefined || a === null || a === "") {
|
|
182
|
+
return 0
|
|
183
|
+
}
|
|
184
|
+
return -1
|
|
185
|
+
} else if (a === undefined || a === null || a === "") {
|
|
186
|
+
return 1
|
|
187
|
+
}
|
|
188
|
+
return undefined
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function naturalCompare(a, b) {
|
|
192
|
+
if (a[0] === "-" && b[0] === "-") {
|
|
193
|
+
a = a.slice(1, a.length)
|
|
194
|
+
b = b.slice(1, b.length)
|
|
195
|
+
return b.localeCompare(a, undefined, { numeric: true })
|
|
196
|
+
}
|
|
197
|
+
return a.localeCompare(b, undefined, { numeric: true })
|
|
198
|
+
}
|
|
199
|
+
|
|
179
200
|
/**
|
|
180
201
|
* Returns an "ascending" comparator using natural string sort, to be used to sort an array of strings.
|
|
181
|
-
*
|
|
202
|
+
* Empty string or undefined or null values are always sorted to the end.
|
|
182
203
|
* @param {string|number|Function=} transform
|
|
183
204
|
* If a function, calls the provided function on an element to get the value to sort on.
|
|
184
205
|
* If a string or number, treats transform as a key and sorts on each element's value at key.
|
|
@@ -189,22 +210,22 @@ export function naturalAsc(transform) {
|
|
|
189
210
|
return (a, b) => {
|
|
190
211
|
a = transform(a)
|
|
191
212
|
b = transform(b)
|
|
192
|
-
return
|
|
213
|
+
return compareUndefinedNullEmpty(a, b) ?? naturalCompare(a, b)
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
if (typeof transform === "string" || typeof transform === "number") {
|
|
196
217
|
return (a, b) => {
|
|
197
|
-
const invalid =
|
|
198
|
-
return invalid ?? a[transform]
|
|
218
|
+
const invalid = compareUndefinedNullEmpty(a[transform], b[transform])
|
|
219
|
+
return invalid ?? naturalCompare(a[transform], b[transform])
|
|
199
220
|
}
|
|
200
221
|
}
|
|
201
222
|
return (a, b) => {
|
|
202
|
-
return
|
|
223
|
+
return compareUndefinedNullEmpty(a, b) ?? naturalCompare(a, b)
|
|
203
224
|
}
|
|
204
225
|
}
|
|
205
226
|
/**
|
|
206
227
|
* Returns a "descending" comparator using natural string sort, to be used to sort an array of strings.
|
|
207
|
-
*
|
|
228
|
+
* Empty string or undefined or null values are always sorted to the end.
|
|
208
229
|
* @param {string|number|Function=} transform
|
|
209
230
|
* If a function, calls the provided function on an element to get the value to sort on.
|
|
210
231
|
* If a string or number, treats transform as a key and sorts on each element's value at key.
|
|
@@ -215,17 +236,17 @@ export function naturalDesc(transform) {
|
|
|
215
236
|
return (a, b) => {
|
|
216
237
|
b = transform(b)
|
|
217
238
|
a = transform(a)
|
|
218
|
-
return
|
|
239
|
+
return compareUndefinedNullEmpty(a, b) ?? naturalCompare(b, a)
|
|
219
240
|
}
|
|
220
241
|
}
|
|
221
242
|
if (typeof transform === "string" || typeof transform === "number") {
|
|
222
243
|
return (a, b) => {
|
|
223
|
-
const invalid =
|
|
224
|
-
return invalid ?? b[transform]
|
|
244
|
+
const invalid = compareUndefinedNullEmpty(a[transform], b[transform])
|
|
245
|
+
return invalid ?? naturalCompare(b[transform], a[transform])
|
|
225
246
|
}
|
|
226
247
|
}
|
|
227
248
|
return (a, b) => {
|
|
228
|
-
return
|
|
249
|
+
return compareUndefinedNullEmpty(a, b) ?? naturalCompare(b, a)
|
|
229
250
|
}
|
|
230
251
|
}
|
|
231
252
|
|
package/src/array.test.js
CHANGED
|
@@ -511,12 +511,24 @@ describe("naturalAsc", () => {
|
|
|
511
511
|
expect(arr.map((o) => o.v)).toEqual(["file1", "file2", "file10", undefined, null])
|
|
512
512
|
})
|
|
513
513
|
|
|
514
|
+
it("defers empty string to the end along with undefined/null (default path)", () => {
|
|
515
|
+
const arr = ["b", "", "a", undefined, null, "c"]
|
|
516
|
+
arr.sort(naturalAsc())
|
|
517
|
+
expect(arr).toEqual(["a", "b", "c", "", null, undefined])
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it("defers empty string to the end along with undefined/null (key path)", () => {
|
|
521
|
+
const arr = [{ v: "" }, { v: "a2" }, { v: undefined }, { v: "a1" }, { v: null }]
|
|
522
|
+
arr.sort(naturalAsc("v"))
|
|
523
|
+
expect(arr.map((o) => o.v)).toEqual(["a1", "a2", "", undefined, null])
|
|
524
|
+
})
|
|
525
|
+
|
|
514
526
|
it("returns 0 for equal values (function and key paths)", () => {
|
|
515
527
|
expect(naturalAsc((x) => x)("a", "a")).toBe(0)
|
|
516
528
|
expect(naturalAsc("v")({ v: "x" }, { v: "x" })).toBe(0)
|
|
517
529
|
})
|
|
518
530
|
|
|
519
|
-
it("default comparator sorts with undefined/null at end
|
|
531
|
+
it("default comparator sorts naturally with undefined/null at end", () => {
|
|
520
532
|
const arr = [undefined, "b", null, "a", "c"]
|
|
521
533
|
arr.sort(naturalAsc())
|
|
522
534
|
expect(arr).toEqual(["a", "b", "c", null, undefined])
|
|
@@ -524,6 +536,12 @@ describe("naturalAsc", () => {
|
|
|
524
536
|
numericLike.sort(naturalAsc())
|
|
525
537
|
expect(numericLike).toEqual(["a1", "a2", "a10"])
|
|
526
538
|
})
|
|
539
|
+
|
|
540
|
+
it("handles negative number-like strings by comparing magnitudes (both negative)", () => {
|
|
541
|
+
const arr = ["-2", "-10", "-1"]
|
|
542
|
+
arr.sort(naturalAsc())
|
|
543
|
+
expect(arr).toEqual(["-10", "-2", "-1"])
|
|
544
|
+
})
|
|
527
545
|
})
|
|
528
546
|
|
|
529
547
|
describe("naturalDesc", () => {
|
|
@@ -551,10 +569,23 @@ describe("naturalDesc", () => {
|
|
|
551
569
|
expect(arr.map((o) => o.v)).toEqual(["file10", "file2", "file1", undefined, null])
|
|
552
570
|
})
|
|
553
571
|
|
|
572
|
+
it("defers empty string to the end along with undefined/null (default path)", () => {
|
|
573
|
+
const arr = ["b", "", "a", undefined, null, "c"]
|
|
574
|
+
arr.sort(naturalDesc())
|
|
575
|
+
expect(arr.slice(-3)).toEqual(["", null, undefined])
|
|
576
|
+
expect(arr.slice(0, 3)).toEqual(["c", "b", "a"])
|
|
577
|
+
})
|
|
578
|
+
|
|
554
579
|
it("returns 0 for equal values (function and key paths)", () => {
|
|
555
580
|
expect(naturalDesc((x) => x)("a", "a")).toBe(0)
|
|
556
581
|
expect(naturalDesc("v")({ v: "x" }, { v: "x" })).toBe(0)
|
|
557
582
|
})
|
|
583
|
+
|
|
584
|
+
it("handles negative number-like strings by comparing magnitudes (both negative) in descending", () => {
|
|
585
|
+
const arr = ["-2", "-10", "-1"]
|
|
586
|
+
arr.sort(naturalDesc())
|
|
587
|
+
expect(arr).toEqual(["-1", "-2", "-10"])
|
|
588
|
+
})
|
|
558
589
|
})
|
|
559
590
|
|
|
560
591
|
describe("multilevel", () => {
|
package/src/math.js
CHANGED
|
@@ -176,7 +176,7 @@ export function quantiles(array, { N, key, method = Math.round, labeller = Math.
|
|
|
176
176
|
return undefined
|
|
177
177
|
}
|
|
178
178
|
const sorted = [...array].sort(ascending(key))
|
|
179
|
-
const result =
|
|
179
|
+
const result = Object.create(null)
|
|
180
180
|
for (let i = 0; i <= N; i++) {
|
|
181
181
|
const percentile = i / N
|
|
182
182
|
const percentileIndex = method(percentile * (sorted.length - 1))
|
package/src/object.js
CHANGED
|
@@ -15,7 +15,7 @@ export function isObject(thing) {
|
|
|
15
15
|
* @returns {Object}
|
|
16
16
|
*/
|
|
17
17
|
export function mapValues(object, callback) {
|
|
18
|
-
const result =
|
|
18
|
+
const result = Object.create(null)
|
|
19
19
|
const keys = Object.keys(object)
|
|
20
20
|
for (const key of keys) {
|
|
21
21
|
result[key] = callback(object[key], key, object)
|
package/src/run.js
CHANGED
|
@@ -10,7 +10,7 @@ import { pathToFileURL } from "url"
|
|
|
10
10
|
export function runnable(importMetaUrl, args, callback) {
|
|
11
11
|
if (importMetaUrl === pathToFileURL(process.argv[1]).href) {
|
|
12
12
|
// module was not imported but called directly
|
|
13
|
-
const parameter =
|
|
13
|
+
const parameter = Object.create(null)
|
|
14
14
|
for (let i = 0; i < args.length; i++) {
|
|
15
15
|
parameter[args[i]] = process.argv[i + 2]
|
|
16
16
|
}
|