From ced155925f4cc8fae9d17ac1953f8f80ccf70423 Mon Sep 17 00:00:00 2001 From: Ewan Breakey Date: Thu, 25 May 2023 13:16:25 +1000 Subject: [PATCH] Add splitArrayBy utility --- .changeset/fresh-elephants-hide.md | 5 +++ lib/arrays/index.ts | 1 + lib/arrays/splitArrayBy.ts | 60 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 .changeset/fresh-elephants-hide.md create mode 100644 lib/arrays/splitArrayBy.ts diff --git a/.changeset/fresh-elephants-hide.md b/.changeset/fresh-elephants-hide.md new file mode 100644 index 0000000..5b49ba1 --- /dev/null +++ b/.changeset/fresh-elephants-hide.md @@ -0,0 +1,5 @@ +--- +"@giraugh/tools": minor +--- + +Add splitArrayBy utility diff --git a/lib/arrays/index.ts b/lib/arrays/index.ts index 11c1750..a3b5521 100644 --- a/lib/arrays/index.ts +++ b/lib/arrays/index.ts @@ -6,3 +6,4 @@ export * from './zipArrays' export * from './zipArraysLongest' export * from './partitionArray' export * from './rotateArray' +export * from './splitArrayBy' diff --git a/lib/arrays/splitArrayBy.ts b/lib/arrays/splitArrayBy.ts new file mode 100644 index 0000000..f10d777 --- /dev/null +++ b/lib/arrays/splitArrayBy.ts @@ -0,0 +1,60 @@ +/** + * Split an array when a predicate function returns true and return all the groups + * @param arr the array to split + * @param splitFn a function over pairs of elements in the array that returns true when the elements should be in different groups + * @returns the array of groups split as per the `splitFn` + * + * @example + * const runs = splitArrayBy([1, 2, 3, 10, 11, 12], (a, b) => b - a > 1) + * runs // [[1, 2, 3], [10, 11, 12]] + */ +export const splitArrayBy = (arr: T[], splitFn: (a: T, b: T) => boolean): T[][] => { + // Handle special case of empty array + if (arr.length === 0) return [] + + let groups: T[][] = [[]] + while (true) { + // Shift one element and peak the next element + if (arr.length === 0) break + let [x, y] = [arr.shift()!, arr.at(0)] + + // Add element to current group + groups.at(-1)!.push(x) + + // Start new group? + if (arr.length > 0 && splitFn(x, y!)) + groups.push([]) + } + + return groups +} + +if (import.meta.vitest) { + const { expect, it } = import.meta.vitest + + it('works for example 1', () => { + const runs = splitArrayBy([1, 2, 3, 10, 11, 12], (x, y) => Math.abs(y - x) > 1) + expect(runs).toEqual([[1, 2, 3], [10, 11, 12]]) + }) + + it('handles single group', () => { + const groups = splitArrayBy([1, 2, 3], () => false) + expect(groups).toEqual([[1, 2, 3]]) + }) + + it('handles many splits', () => { + const groups = splitArrayBy([1, 2, 3], () => true) + expect(groups).toEqual([[1], [2], [3]]) + }) + + it('handles special case of empty array', () => { + const groups = splitArrayBy([], () => true) + expect(groups).toEqual([]) + }) + + it('handles falsy values', () => { + const groups = splitArrayBy([1, 1, 1, undefined, undefined, 2, 2, 2], (x, y) => !!x !== !!y) + expect(groups).toEqual([[1, 1, 1 ], [undefined, undefined], [2, 2, 2]]) + }) +} +