@tim-code/my-util 0.4.2 → 0.4.4

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.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/find.js CHANGED
@@ -270,70 +270,72 @@ export function findMax(array, { key, cutoff = -Infinity } = {}) {
270
270
  }
271
271
 
272
272
  /**
273
- * Like Array.prototype.findIndex but starts from given index.
273
+ * Find the first truthy value in an array.
274
+ * Supports "from" and "to" being reversed to "find last truthy".
274
275
  * @template T
275
276
  * @param {Array<T>} array
276
- * @param {number} fromIndex
277
- * @param {(value: T, index: number, array: Array<T>) => boolean} callback
278
- * @returns {number}
277
+ * @param {Object} $1
278
+ * @param {string|Function=} $1.key Specifies an alternative to using each element as the value.
279
+ * If string, then accesses each element at that key to get value.
280
+ * If function, then calls the callback on each element to get value.
281
+ * @param {number=} $1.from Numeric index to start from: inclusive, defaults to 0
282
+ * @param {number=} $1.to Numeric index to end at: inclusive, defaults to `array.length - 1`
283
+ * @returns {T}
279
284
  */
280
- export function findIndexFrom(array, fromIndex, callback) {
281
- for (let i = fromIndex; i < array.length; i++) {
282
- if (callback(array[i], i, array)) {
283
- return i
285
+ export function findTruthy(array, { key, from = 0, to = array.length - 1 }) {
286
+ if (typeof key === "function") {
287
+ if (from <= to) {
288
+ for (let i = from; i <= to; i++) {
289
+ const element = array[i]
290
+ const value = key(element, i, array)
291
+ if (value) {
292
+ return element
293
+ }
294
+ }
295
+ } else {
296
+ for (let i = from; i >= to; i--) {
297
+ const element = array[i]
298
+ const value = key(element, i, array)
299
+ if (value) {
300
+ return element
301
+ }
302
+ }
284
303
  }
285
- }
286
- return -1
287
- }
288
-
289
- /**
290
- * Like Array.prototype.find but starts from given index.
291
- * @template T
292
- * @param {Array<T>} array
293
- * @param {number} fromIndex
294
- * @param {(value: T, index: number, array: Array<T>) => boolean} callback
295
- * @returns {T|undefined}
296
- */
297
- export function findFrom(array, fromIndex, callback) {
298
- const foundIndex = findIndexFrom(array, fromIndex, callback)
299
- if (foundIndex < 0) {
300
- return undefined
301
- }
302
- return array[foundIndex]
303
- }
304
-
305
- /**
306
- * Like Array.prototype.findLastIndex but starts from given index.
307
- * @template T
308
- * @param {Array<T>} array
309
- * @param {number} fromIndex If greater or equal to length, coerces to last index in array.
310
- * @param {(value: T, index: number, array: Array<T>) => boolean} callback
311
- * @returns {number}
312
- */
313
- export function findLastIndexFrom(array, fromIndex, callback) {
314
- if (fromIndex >= array.length) {
315
- fromIndex = array.length - 1
316
- }
317
- for (let i = fromIndex; i >= 0; i--) {
318
- if (callback(array[i], i, array)) {
319
- return i
304
+ } else if (typeof key === "number" || typeof key === "string") {
305
+ if (from <= to) {
306
+ for (let i = from; i <= to; i++) {
307
+ const element = array[i]
308
+ const value = element[key]
309
+ if (value) {
310
+ return element
311
+ }
312
+ }
313
+ } else {
314
+ for (let i = from; i >= to; i--) {
315
+ const element = array[i]
316
+ const value = element[key]
317
+ if (value) {
318
+ return element
319
+ }
320
+ }
321
+ }
322
+ } else {
323
+ // eslint-disable-next-line no-lonely-if
324
+ if (from <= to) {
325
+ for (let i = from; i <= to; i++) {
326
+ const value = array[i]
327
+ if (value) {
328
+ return value
329
+ }
330
+ }
331
+ } else {
332
+ for (let i = from; i >= to; i--) {
333
+ const value = array[i]
334
+ if (value) {
335
+ return value
336
+ }
337
+ }
320
338
  }
321
339
  }
322
- return -1
323
- }
324
-
325
- /**
326
- * Like Array.prototype.findLast but starts from given index.
327
- * @template T
328
- * @param {Array<T>} array
329
- * @param {number} fromIndex
330
- * @param {(value: T, index: number, array: Array<T>) => boolean} callback
331
- * @returns {T|undefined}
332
- */
333
- export function findLastFrom(array, fromIndex, callback) {
334
- const foundIndex = findLastIndexFrom(array, fromIndex, callback)
335
- if (foundIndex < 0) {
336
- return undefined
337
- }
338
- return array[foundIndex]
340
+ return undefined
339
341
  }
package/src/find.test.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-restricted-syntax */
1
2
  import { describe, expect, it } from "@jest/globals"
2
3
 
3
4
  const {
@@ -9,10 +10,7 @@ const {
9
10
  findClosest,
10
11
  findMin,
11
12
  findMax,
12
- findIndexFrom,
13
- findFrom,
14
- findLastIndexFrom,
15
- findLastFrom,
13
+ findTruthy,
16
14
  } = await import("./find.js")
17
15
 
18
16
  describe("findClosestAbs", () => {
@@ -289,116 +287,54 @@ describe("findMax", () => {
289
287
  })
290
288
  })
291
289
 
292
- describe("findIndexFrom", () => {
293
- it("returns the index of the first matching element from fromIndex", () => {
294
- expect(findIndexFrom([1, 2, 3, 4], 1, (x) => x > 2)).toBe(2)
295
- expect(findIndexFrom([1, 2, 3, 4], 2, (x) => x > 2)).toBe(2)
296
- expect(findIndexFrom([1, 2, 3, 4], 3, (x) => x > 2)).toBe(3)
290
+ describe("findTruthy", () => {
291
+ it("returns the first truthy value in a simple array", () => {
292
+ expect(findTruthy([0, false, null, 2, 3], {})).toBe(2)
293
+ expect(findTruthy([0, false, null, undefined], {})).toBeUndefined()
297
294
  })
298
295
 
299
- it("returns -1 if no element matches", () => {
300
- expect(findIndexFrom([1, 2, 3, 4], 2, (x) => x > 10)).toBe(-1)
296
+ it("returns the first truthy value in a range [from, to]", () => {
297
+ expect(findTruthy([0, 1, 2, 0, 3, 0], { from: 2, to: 5 })).toBe(2)
298
+ expect(findTruthy([0, 0, 0, 4, 0], { from: 1, to: 3 })).toBe(4)
299
+ expect(findTruthy([0, 0, 0, 0], { from: 1, to: 2 })).toBeUndefined()
301
300
  })
302
301
 
303
- it("returns -1 for empty array", () => {
304
- expect(findIndexFrom([], 0, () => true)).toBe(-1)
302
+ it("returns the first truthy value when searching backwards (from > to)", () => {
303
+ expect(findTruthy([0, 1, 2, 0, 3, 0], { from: 4, to: 1 })).toBe(3)
304
+ expect(findTruthy([0, 0, 0, 4, 0], { from: 3, to: 0 })).toBe(4)
305
+ expect(findTruthy([0, 0, 0, 0], { from: 2, to: 0 })).toBeUndefined()
305
306
  })
306
307
 
307
- it("passes correct arguments to callback", () => {
308
- const arr = [10, 20, 30]
309
- const called = []
310
- findIndexFrom(arr, 1, (value, index, array) => {
311
- called.push([value, index, array])
312
- return false
313
- })
314
- expect(called[0][0]).toBe(20)
315
- expect(called[0][1]).toBe(1)
316
- expect(called[0][2]).toBe(arr)
308
+ it("supports key as function", () => {
309
+ const arr = [{ v: 0 }, { v: 2 }, { v: 0 }]
310
+ expect(findTruthy(arr, { key: (e) => e.v })).toEqual({ v: 2 })
311
+ expect(findTruthy(arr, { key: (e) => e.v, from: 2, to: 0 })).toEqual({ v: 2 })
312
+ expect(findTruthy(arr, { key: (e) => e.v, from: 0, to: 0 })).toBeUndefined()
317
313
  })
318
- })
319
314
 
320
- describe("findFrom", () => {
321
- it("returns the first matching element from fromIndex", () => {
322
- expect(findFrom([1, 2, 3, 4], 1, (x) => x > 2)).toBe(3)
323
- expect(findFrom([1, 2, 3, 4], 2, (x) => x > 2)).toBe(3)
324
- expect(findFrom([1, 2, 3, 4], 3, (x) => x > 2)).toBe(4)
315
+ it("supports key as string", () => {
316
+ const arr = [{ x: 0 }, { x: 2 }, { x: 0 }]
317
+ expect(findTruthy(arr, { key: "x" })).toEqual({ x: 2 })
318
+ expect(findTruthy(arr, { key: "x", from: 2, to: 0 })).toEqual({ x: 2 })
319
+ expect(findTruthy(arr, { key: "x", from: 0, to: 0 })).toBeUndefined()
325
320
  })
326
321
 
327
- it("returns undefined if no element matches", () => {
328
- expect(findFrom([1, 2, 3, 4], 2, (x) => x > 10)).toBeUndefined()
322
+ it("supports key as number", () => {
323
+ const arr = [[0], [2], [0]]
324
+ expect(findTruthy(arr, { key: 0 })).toEqual([2])
325
+ expect(findTruthy(arr, { key: 0, from: 2, to: 0 })).toEqual([2])
326
+ expect(findTruthy(arr, { key: 0, from: 0, to: 0 })).toBeUndefined()
329
327
  })
330
328
 
331
329
  it("returns undefined for empty array", () => {
332
- expect(findFrom([], 0, () => true)).toBeUndefined()
333
- })
334
-
335
- it("passes correct arguments to callback", () => {
336
- const arr = [10, 20, 30]
337
- const called = []
338
- findFrom(arr, 1, (value, index, array) => {
339
- called.push([value, index, array])
340
- return false
341
- })
342
- expect(called[0][0]).toBe(20)
343
- expect(called[0][1]).toBe(1)
344
- expect(called[0][2]).toBe(arr)
345
- })
346
- })
347
-
348
- describe("findLastIndexFrom", () => {
349
- it("returns the index of the last matching element from fromIndex (backwards)", () => {
350
- expect(findLastIndexFrom([1, 2, 3, 4], 2, (x) => x < 3)).toBe(1)
351
- expect(findLastIndexFrom([1, 2, 3, 4], 3, (x) => x < 3)).toBe(1)
352
- expect(findLastIndexFrom([1, 2, 3, 4], 1, (x) => x < 3)).toBe(1)
353
- expect(findLastIndexFrom([1, 2, 3, 4], 0, (x) => x < 3)).toBe(0)
354
- })
355
-
356
- it("returns -1 if no element matches", () => {
357
- expect(findLastIndexFrom([1, 2, 3, 4], 3, (x) => x > 10)).toBe(-1)
330
+ expect(findTruthy([], {})).toBeUndefined()
331
+ expect(findTruthy([], { from: 0, to: 0 })).toBeUndefined()
358
332
  })
359
333
 
360
- it("returns -1 for empty array", () => {
361
- expect(findLastIndexFrom([], 0, () => true)).toBe(-1)
362
- })
363
-
364
- it("passes correct arguments to callback", () => {
365
- const arr = [10, 20, 30]
366
- const called = []
367
- findLastIndexFrom(arr, 2, (value, index, array) => {
368
- called.push([value, index, array])
369
- return false
370
- })
371
- expect(called[0][0]).toBe(30)
372
- expect(called[0][1]).toBe(2)
373
- expect(called[0][2]).toBe(arr)
374
- })
375
- })
376
-
377
- describe("findLastFrom", () => {
378
- it("returns the last matching element from fromIndex (backwards)", () => {
379
- expect(findLastFrom([1, 2, 3, 4], 2, (x) => x < 3)).toBe(2)
380
- expect(findLastFrom([1, 2, 3, 4], 3, (x) => x < 3)).toBe(2)
381
- expect(findLastFrom([1, 2, 3, 4], 1, (x) => x < 3)).toBe(2)
382
- expect(findLastFrom([1, 2, 3, 4], 0, (x) => x < 3)).toBe(1)
383
- })
384
-
385
- it("returns undefined if no element matches", () => {
386
- expect(findLastFrom([1, 2, 3, 4], 3, (x) => x > 10)).toBeUndefined()
387
- })
388
-
389
- it("returns undefined for empty array", () => {
390
- expect(findLastFrom([], 0, () => true)).toBeUndefined()
391
- })
392
-
393
- it("passes correct arguments to callback", () => {
394
- const arr = [10, 20, 30]
395
- const called = []
396
- findLastFrom(arr, 2, (value, index, array) => {
397
- called.push([value, index, array])
398
- return false
399
- })
400
- expect(called[0][0]).toBe(30)
401
- expect(called[0][1]).toBe(2)
402
- expect(called[0][2]).toBe(arr)
334
+ it("returns undefined if from/to out of bounds", () => {
335
+ expect(findTruthy([1, 2, 3], { from: 10, to: 12 })).toBeUndefined()
336
+ expect(findTruthy([1, 2, 3], { from: -5, to: -1 })).toBeUndefined()
337
+ expect(findTruthy([1, 2, 3], { from: 2, to: 10 })).toBe(3)
338
+ expect(findTruthy([1, 2, 3], { from: 10, to: 2 })).toBe(3)
403
339
  })
404
340
  })