remeda
Version:
A utility library for JavaScript and Typescript.
1 lines • 5.59 kB
Source Map (JSON)
{"version":3,"file":"firstBy.cjs","names":["purryOrderRules","hasAtLeast"],"sources":["../src/firstBy.ts"],"sourcesContent":["import { hasAtLeast } from \"./hasAtLeast\";\nimport { purryOrderRules, type OrderRule } from \"./internal/purryOrderRules\";\nimport type { CompareFunction } from \"./internal/types/CompareFunction\";\nimport type { IterableContainer } from \"./internal/types/IterableContainer\";\nimport type { NonEmptyArray } from \"./internal/types/NonEmptyArray\";\n\ntype FirstBy<T extends IterableContainer> =\n | T[number]\n | (T extends readonly [unknown, ...ReadonlyArray<unknown>]\n ? never\n : T extends readonly [...ReadonlyArray<unknown>, unknown]\n ? never\n : undefined);\n\n/**\n * Find the first element in the array that adheres to the order rules provided. This is a superset of what a typical `maxBy` or `minBy` function would do as it allows defining \"tie-breaker\" rules when values are equal, and allows comparing items using any logic. This function is equivalent to calling `R.first(R.sortBy(...))` but runs at *O(n)* instead of *O(nlogn)*.\n *\n * Use `nthBy` if you need an element other that the first, or `takeFirstBy` if you more than just the first element.\n *\n * @param rules - A variadic array of order rules defining the sorting criteria. Each order rule is a projection function that extracts a comparable value from the data. Sorting is based on these extracted values using the native `<` and `>` operators. Earlier rules take precedence over later ones. Use the syntax `[projection, \"desc\"]` for descending order.\n * @returns The first element by the order criteria, or `undefined` if the array\n * is empty. (The function provides strong typing if the input type assures the\n * array isn't empty).\n * @signature\n * R.firstBy(...rules)(data);\n * @example\n * const max = R.pipe([1,2,3], R.firstBy([R.identity(), \"desc\"])); // => 3;\n * const min = R.pipe([1,2,3], R.firstBy(R.identity())); // => 1;\n *\n * const data = [{ a: \"a\" }, { a: \"aa\" }, { a: \"aaa\" }] as const;\n * const maxBy = R.pipe(data, R.firstBy([(item) => item.a.length, \"desc\"])); // => { a: \"aaa\" };\n * const minBy = R.pipe(data, R.firstBy((item) => item.a.length)); // => { a: \"a\" };\n *\n * const data = [{type: \"cat\", size: 1}, {type: \"cat\", size: 2}, {type: \"dog\", size: 3}] as const;\n * const multi = R.pipe(data, R.firstBy(R.prop('type'), [R.prop('size'), 'desc'])); // => {type: \"cat\", size: 2}\n * @dataLast\n * @category Array\n */\nexport function firstBy<T extends IterableContainer>(\n ...rules: Readonly<NonEmptyArray<OrderRule<T[number]>>>\n): (data: T) => FirstBy<T>;\n\n/**\n * Find the first element in the array that adheres to the order rules provided. This is a superset of what a typical `maxBy` or `minBy` function would do as it allows defining \"tie-breaker\" rules when values are equal, and allows comparing items using any logic. This function is equivalent to calling `R.first(R.sortBy(...))` but runs at *O(n)* instead of *O(nlogn)*.\n *\n * Use `nthBy` if you need an element other that the first, or `takeFirstBy` if you more than just the first element.\n *\n * @param data - An array of items.\n * @param rules - A variadic array of order rules defining the sorting criteria. Each order rule is a projection function that extracts a comparable value from the data. Sorting is based on these extracted values using the native `<` and `>` operators. Earlier rules take precedence over later ones. Use the syntax `[projection, \"desc\"]` for descending order.\n * @returns The first element by the order criteria, or `undefined` if the array\n * is empty. (The function provides strong typing if the input type assures the\n * array isn't empty).\n * @signature\n * R.firstBy(data, ...rules);\n * @example\n * const max = R.firstBy([1,2,3], [R.identity(), \"desc\"]); // => 3;\n * const min = R.firstBy([1,2,3], R.identity()); // => 1;\n *\n * const data = [{ a: \"a\" }, { a: \"aa\" }, { a: \"aaa\" }] as const;\n * const maxBy = R.firstBy(data, [(item) => item.a.length, \"desc\"]); // => { a: \"aaa\" };\n * const minBy = R.firstBy(data, (item) => item.a.length); // => { a: \"a\" };\n *\n * const data = [{type: \"cat\", size: 1}, {type: \"cat\", size: 2}, {type: \"dog\", size: 3}] as const;\n * const multi = R.firstBy(data, R.prop('type'), [R.prop('size'), 'desc']); // => {type: \"cat\", size: 2}\n * @dataFirst\n * @category Array\n */\nexport function firstBy<T extends IterableContainer>(\n data: T,\n ...rules: Readonly<NonEmptyArray<OrderRule<T[number]>>>\n): FirstBy<T>;\n\nexport function firstBy(...args: ReadonlyArray<unknown>): unknown {\n return purryOrderRules(firstByImplementation, args);\n}\n\nfunction firstByImplementation<T>(\n data: ReadonlyArray<T>,\n compareFn: CompareFunction<T>,\n): T | undefined {\n if (!hasAtLeast(data, 2)) {\n // If we have 0 or 1 item we simply return the trivial result.\n return data[0];\n }\n\n let [currentFirst] = data;\n\n // Remove the first item, we won't compare it with itself.\n const [, ...rest] = data;\n for (const item of rest) {\n if (compareFn(item, currentFirst) < 0) {\n // item comes before currentFirst in the order.\n currentFirst = item;\n }\n }\n\n return currentFirst;\n}\n"],"mappings":"yFAwEA,SAAgB,EAAQ,GAAG,EAAuC,CAChE,OAAOA,EAAAA,EAAgB,EAAuB,EAAK,CAGrD,SAAS,EACP,EACA,EACe,CACf,GAAI,CAACC,EAAAA,EAAW,EAAM,EAAE,CAEtB,OAAO,EAAK,GAGd,GAAI,CAAC,GAAgB,EAGf,EAAG,GAAG,GAAQ,EACpB,IAAK,IAAM,KAAQ,EACb,EAAU,EAAM,EAAa,CAAG,IAElC,EAAe,GAInB,OAAO"}