Skip to content

Instantly share code, notes, and snippets.

@sirlancelot
Last active March 13, 2025 22:38
Show Gist options
  • Save sirlancelot/4d00e60303464663262f8f198ae3ea3c to your computer and use it in GitHub Desktop.
Save sirlancelot/4d00e60303464663262f8f198ae3ea3c to your computer and use it in GitHub Desktop.
/**
 * Split an array into buckets based on the predicates. The size of the returned
 * array of buckets is equal to the number of predicates + 1. The last bucket
 * contains all the items that did not match any predicate.
 * 
 * The first matching predicate wins.
 *
 * Overload 1: Strongly typed predicates for type-narrowing
 */
export function partitionBy<T, R extends T[]>(
	arr: T[],
	...predicates: { [K in keyof R]: (item: T) => item is R[K] }
): [...{ [K in keyof R]: R[K][] }, Exclude<T, R[number]>[]]
/**
 * Split an array into buckets based on the predicates. The size of the returned
 * array of buckets is equal to the number of predicates + 1. The last bucket
 * contains all the items that did not match any predicate.
 * 
 * The first matching predicate wins.
 *
 * Overload 2: When type-narrowing cannot be inferred, use the input type
 */
export function partitionBy<T>(
	arr: T[],
	...predicates: Array<(item: T) => boolean>
): T[][]
/** Implementation */
export function partitionBy<T>(
	arr: T[],
	...predicates: Array<(item: T) => boolean>
): T[][] {
	const output: T[][] = Array.from(
		{ length: predicates.length + 1 },
		() => []
	)

	for (const [index, item] of arr.entries()) {
		const index = predicates.findIndex((predicate) => predicate(item))
		output.at(index)!.push(item)
	}

	return output
}

Usage

interface SystemParam {
    role: "system"
}
interface UserParam {
    role: "user"
}
type ChatParam = SystemParam | UserParam

let messages: ChatParam[] = []

// Overload 1: `system` and `user` arrays are correctly typed
let [system, user] = partitionBy(
    messages,
    (item) => item.role === "system"
)

// Overload 2: When no type-narrowing is determined
let [odd, even] = partitionBy(
	[1, 2, 3, 4, 5, 6, 7, 8, 9, 0],
	(item) => item % 2 === 0
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment