Last active
June 22, 2021 16:03
-
-
Save sidola/57514394877bd8ff1c76fe2c232c8ec5 to your computer and use it in GitHub Desktop.
Chain sorting, with Typescript support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* A type that only allows primitive property keys | |
*/ | |
type PrimitiveKeysOnly<T> = { | |
[P in keyof T]: T[P] extends number | string | boolean ? P : never | |
}[keyof T] | |
type SortableProp<T> = { | |
prop: PrimitiveKeysOnly<T> | |
order: 'desc' | 'asc' | |
} | |
/** | |
* Sorts the given list by the natural sort order of the given | |
* property types. The original list is not mutated. | |
* | |
* @param items The items to sort. | |
* | |
* @param sortBy The property keys to sort, each key will be tried in | |
* the order they're given, once a key results in a non-equal order, | |
* that order is used. | |
* | |
* @example | |
* chainSort( | |
* [{ name: "Alice", age: 32 }, { name: "Bob", age: 24 }], | |
* ['name', { prop: 'age', order: 'desc' }] | |
* ) | |
*/ | |
export function chainSort<T>( | |
items: T[], | |
sortBy: (PrimitiveKeysOnly<T> | SortableProp<T>)[] | |
): T[] { | |
const sorted = [...items].sort((a, b) => { | |
const sortResult = sortBy.reduce<number | null>((acc, curr) => { | |
const resolveValue = ( | |
key: PrimitiveKeysOnly<T> | SortableProp<T>, | |
obj1: T, | |
obj2: T | |
) => { | |
if (typeof key === 'object') { | |
return { | |
valueA: obj1[key.prop], | |
valueB: obj2[key.prop], | |
order: key.order | |
} | |
} else { | |
return { | |
valueA: obj1[key], | |
valueB: obj2[key], | |
order: 'asc' | |
} | |
} | |
} | |
const { valueA, valueB, order } = resolveValue(curr, a, b) | |
// We got a non-equal result, this means we're done | |
// sorting and are just waiting to iterate through all the | |
// props. | |
if (acc != null && acc !== 0) { | |
return acc | |
} | |
if (typeof valueA === "number" && typeof valueB === "number") { | |
// a - b = ascending (0, 1, 2, 3, ...) | |
acc = valueA - valueB | |
} | |
if (typeof valueA === "string" && typeof valueB === "string") { | |
// a.compare(b) = ascending (a, b, c, d, ...) | |
acc = valueA.localeCompare(valueB) | |
} | |
if (typeof valueA === "boolean" && typeof valueB === "boolean") { | |
// true, true, false, false, ... | |
return ( | |
// js if fine with this | |
(valueB as unknown as number) - (valueA as unknown as number) | |
) | |
} | |
if (acc == null) { | |
throw Error(`Unsupported prop type, [${typeof valueA}, ${typeof valueB}]`) | |
} | |
if (order === 'desc' && acc !== 0) { | |
// Multiplying by -1 flips the sign. | |
// | |
// It's ok to this now because this is either the last | |
// iteration, or we'll get caught and stopped on the | |
// next iteration because of not being null or 0. | |
return acc * -1 | |
} | |
return acc | |
}, null) | |
if (sortResult == null) { | |
throw Error(`Failed to sort list, sort result came back null`) | |
} | |
return sortResult | |
}) | |
return sorted | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment