Skip to content

Instantly share code, notes, and snippets.

@sidola
Last active June 22, 2021 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sidola/57514394877bd8ff1c76fe2c232c8ec5 to your computer and use it in GitHub Desktop.
Save sidola/57514394877bd8ff1c76fe2c232c8ec5 to your computer and use it in GitHub Desktop.
Chain sorting, with Typescript support
/**
* 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