diff --git a/devTools/svgTester/chart-configurations.ts b/devTools/svgTester/chart-configurations.ts index 87d5220895c..a36d8444455 100644 --- a/devTools/svgTester/chart-configurations.ts +++ b/devTools/svgTester/chart-configurations.ts @@ -6,6 +6,7 @@ import { FacetStrategy, GrapherQueryParams, } from "@ourworldindata/grapher" +import { cartesian } from "@ourworldindata/utils" export type ViewMatrix = Record @@ -147,15 +148,6 @@ export const queryStringsByChartType = Object.fromEntries( }) ) as Record -// adapted from https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript -function cartesian(matrix: any[][]) { - if (matrix.length === 0) return [] - if (matrix.length === 1) return matrix[0].map((i) => [i]) - return matrix.reduce((acc, curr) => - acc.flatMap((i: any) => curr.map((j: any) => [i, j].flat())) - ) -} - function toQueryStr(params: Record): string { return new URLSearchParams(params).toString() } diff --git a/packages/@ourworldindata/utils/src/Util.test.ts b/packages/@ourworldindata/utils/src/Util.test.ts index 6256dbecd88..7db8a6b79c3 100755 --- a/packages/@ourworldindata/utils/src/Util.test.ts +++ b/packages/@ourworldindata/utils/src/Util.test.ts @@ -28,6 +28,7 @@ import { greatestCommonDivisor, findGreatestCommonDivisorOfArray, traverseEnrichedBlocks, + cartesian, } from "./Util.js" import { BlockImageSize, @@ -747,3 +748,47 @@ describe(traverseEnrichedBlocks, () => { ]) }) }) + +describe(cartesian, () => { + it("returns an empty list when no arrays are provided", () => { + expect(cartesian([])).toEqual([]) + }) + + it("returns individual items if a single array is given", () => { + expect(cartesian([["a", "b"]])).toEqual([["a"], ["b"]]) + }) + + it("returns all possible combinations if multiple arrays are given", () => { + expect(cartesian([["a", "b"], ["x"]])).toEqual([ + ["a", "x"], + ["b", "x"], + ]) + expect( + cartesian([ + ["a", "b"], + ["x", "y"], + ]) + ).toEqual([ + ["a", "x"], + ["a", "y"], + ["b", "x"], + ["b", "y"], + ]) + expect( + cartesian([ + ["a", "b"], + ["x", "y"], + ["+", "-"], + ]) + ).toEqual([ + ["a", "x", "+"], + ["a", "x", "-"], + ["a", "y", "+"], + ["a", "y", "-"], + ["b", "x", "+"], + ["b", "x", "-"], + ["b", "y", "+"], + ["b", "y", "-"], + ]) + }) +}) diff --git a/packages/@ourworldindata/utils/src/Util.ts b/packages/@ourworldindata/utils/src/Util.ts index b3e306c5ccd..e06849c8a84 100644 --- a/packages/@ourworldindata/utils/src/Util.ts +++ b/packages/@ourworldindata/utils/src/Util.ts @@ -1783,3 +1783,17 @@ export function checkIsDataInsight( const type = get(x, "content.type") return type === OwidGdocType.DataInsight } + +/** + * Returns the cartesian product of the given arrays. + * + * For example, `cartesian([["a", "b"], ["x", "y"]])` returns `[["a", "x"], ["a", "y"], ["b", "x"], ["b", "y"]]` + */ +export function cartesian(matrix: T[][]): T[][] { + if (matrix.length === 0) return [] + if (matrix.length === 1) return matrix[0].map((i) => [i]) + return matrix.reduce( + (acc, curr) => acc.flatMap((i) => curr.map((j) => [...i, j])), + [[]] + ) +} diff --git a/packages/@ourworldindata/utils/src/index.ts b/packages/@ourworldindata/utils/src/index.ts index 731970dc23b..7587daa0ee4 100644 --- a/packages/@ourworldindata/utils/src/index.ts +++ b/packages/@ourworldindata/utils/src/index.ts @@ -116,6 +116,7 @@ export { copyToClipboard, checkIsGdocPost, checkIsDataInsight, + cartesian, } from "./Util.js" export {