UNPKG

remeda

Version:

A utility library for JavaScript and Typescript.

1 lines 6.72 kB
{"version":3,"file":"swapIndices.cjs","names":["purry"],"sources":["../src/swapIndices.ts"],"sourcesContent":["import type { IsEqual, Join } from \"type-fest\";\nimport type { IterableContainer } from \"./internal/types/IterableContainer\";\nimport { purry } from \"./purry\";\n\ntype Difference<A extends number, B extends number> =\n TupleOfLength<A> extends [...infer U, ...TupleOfLength<B>]\n ? U[\"length\"]\n : never;\n\ntype isLessThan<A extends number, B extends number> =\n IsEqual<A, B> extends true\n ? false\n : 0 extends A\n ? true\n : 0 extends B\n ? false\n : isLessThan<Difference<A, 1>, Difference<B, 1>>;\n\ntype TupleOfLength<\n L extends number,\n T extends IterableContainer = [],\n> = T[\"length\"] extends L ? T : TupleOfLength<L, [...T, unknown]>;\n\ntype IsNonNegative<T extends number> = number extends T\n ? false\n : `${T}` extends `-${string}`\n ? false\n : true;\n\ntype CharactersTuple<T extends string> = string extends T\n ? Array<string>\n : T extends `${infer C}${infer R}`\n ? [C, ...CharactersTuple<R>]\n : [];\n\ntype SwapArrayInternal<\n T extends IterableContainer,\n Index1 extends number,\n Index2 extends number,\n Position extends ReadonlyArray<unknown> = [],\n Original extends IterableContainer = T,\n> = T extends readonly [infer AtPosition, ...infer Rest]\n ? [\n Position[\"length\"] extends Index1\n ? Original[Index2]\n : Position[\"length\"] extends Index2\n ? Original[Index1]\n : AtPosition,\n ...SwapArrayInternal<\n Rest,\n Index1,\n Index2,\n [unknown, ...Position],\n Original\n >,\n ]\n : T;\n\ntype SwapString<T extends string, K1 extends number, K2 extends number> = Join<\n SwapArray<CharactersTuple<T>, K1, K2>,\n \"\"\n>;\n\ntype SwapArray<\n T extends IterableContainer,\n K1 extends number,\n K2 extends number,\n> =\n // TODO: Because of limitations on the typescript version used in Remeda we can't build a proper Absolute number type so we can't implement proper typing for negative indices and have to opt for a less- strict type instead. Check out the history for the PR that introduced this TODO to see how it could be implemented.\n IsNonNegative<K1> extends false\n ? Array<T[number]>\n : IsNonNegative<K2> extends false\n ? Array<T[number]>\n : // If the indices are not within the input arrays range the result would be\n // trivially the same as the input array.\n isLessThan<K1, T[\"length\"]> extends false\n ? T\n : isLessThan<K2, T[\"length\"]> extends false\n ? T\n : SwapArrayInternal<T, K1, K2>;\n\ntype SwappedIndices<\n T extends IterableContainer | string,\n K1 extends number,\n K2 extends number,\n> = T extends string\n ? SwapString<T, K1, K2>\n : T extends IterableContainer\n ? SwapArray<T, K1, K2>\n : never;\n\n/**\n * Swaps the positions of two elements in an array or string at the provided indices.\n *\n * Negative indices are supported and would be treated as an offset from the end of the array. The resulting type thought would be less strict than when using positive indices.\n *\n * If either index is out of bounds the result would be a shallow copy of the input, as-is.\n *\n * @param data - The item to be manipulated. This can be an array, or a string.\n * @param index1 - The first index.\n * @param index2 - The second index.\n * @returns Returns the manipulated array or string.\n * @signature\n * swapIndices(data, index1, index2)\n * @example\n * swapIndices(['a', 'b', 'c'], 0, 1) // => ['b', 'a', 'c']\n * swapIndices(['a', 'b', 'c'], 1, -1) // => ['a', 'c', 'b']\n * swapIndices('abc', 0, 1) // => 'bac'\n * @dataFirst\n * @category Array\n */\nexport function swapIndices<\n T extends IterableContainer | string,\n K1 extends number,\n K2 extends number,\n>(data: T, index1: K1, index2: K2): SwappedIndices<T, K1, K2>;\n\n/**\n * Swaps the positions of two elements in an array or string at the provided indices.\n *\n * Negative indices are supported and would be treated as an offset from the end of the array. The resulting type thought would be less strict than when using positive indices.\n *\n * If either index is out of bounds the result would be a shallow copy of the input, as-is.\n *\n * @param index1 - The first index.\n * @param index2 - The second index.\n * @returns Returns the manipulated array or string.\n * @signature\n * swapIndices(index1, index2)(data)\n * @example\n * swapIndices(0, 1)(['a', 'b', 'c']) // => ['b', 'a', 'c']\n * swapIndices(0, -1)('abc') // => 'cba'\n * @dataLast\n * @category Array\n */\nexport function swapIndices<K1 extends number, K2 extends number>(\n index1: K1,\n index2: K2,\n): <T extends IterableContainer | string>(data: T) => SwappedIndices<T, K1, K2>;\n\nexport function swapIndices(...args: ReadonlyArray<unknown>): unknown {\n return purry(swapIndicesImplementation, args);\n}\n\nconst swapIndicesImplementation = (\n data: IterableContainer | string,\n index1: number,\n index2: number,\n): unknown =>\n typeof data === \"string\"\n ? // eslint-disable-next-line @typescript-eslint/no-misused-spread -- TODO: I'm not sure what the right way to split the string here, there are multiple \"correct\" answers and each one is meaningfully different: https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2521\n swapArray([...data], index1, index2).join(\"\")\n : swapArray(data, index1, index2);\n\nfunction swapArray(\n data: ReadonlyArray<unknown>,\n index1: number,\n index2: number,\n): Array<unknown> {\n const result = [...data];\n\n if (Number.isNaN(index1) || Number.isNaN(index2)) {\n return result;\n }\n\n const positiveIndexA = index1 < 0 ? data.length + index1 : index1;\n const positiveIndexB = index2 < 0 ? data.length + index2 : index2;\n\n if (positiveIndexA < 0 || positiveIndexA > data.length) {\n return result;\n }\n\n if (positiveIndexB < 0 || positiveIndexB > data.length) {\n return result;\n }\n\n result[positiveIndexA] = data[positiveIndexB];\n result[positiveIndexB] = data[positiveIndexA];\n\n return result;\n}\n"],"mappings":"wCA4IA,SAAgB,EAAY,GAAG,EAAuC,CACpE,OAAOA,EAAAA,EAAM,EAA2B,EAAK,CAG/C,MAAM,GACJ,EACA,EACA,IAEA,OAAO,GAAS,SAEZ,EAAU,CAAC,GAAG,EAAK,CAAE,EAAQ,EAAO,CAAC,KAAK,GAAG,CAC7C,EAAU,EAAM,EAAQ,EAAO,CAErC,SAAS,EACP,EACA,EACA,EACgB,CAChB,IAAM,EAAS,CAAC,GAAG,EAAK,CAExB,GAAI,OAAO,MAAM,EAAO,EAAI,OAAO,MAAM,EAAO,CAC9C,OAAO,EAGT,IAAM,EAAiB,EAAS,EAAI,EAAK,OAAS,EAAS,EACrD,EAAiB,EAAS,EAAI,EAAK,OAAS,EAAS,EAa3D,OAXI,EAAiB,GAAK,EAAiB,EAAK,QAI5C,EAAiB,GAAK,EAAiB,EAAK,OACvC,GAGT,EAAO,GAAkB,EAAK,GAC9B,EAAO,GAAkB,EAAK,GAEvB"}