isaacscript-common
Version:
Helper functions and features for IsaacScript mods.
213 lines (186 loc) • 5.59 kB
text/typescript
import { isNumber, isString, isTable } from "./types";
export function sortNormal(a: unknown, b: unknown): -1 | 0 | 1 {
if (!isNumber(a) && !isString(a)) {
error(
`Failed to normal sort since the first value was not a number or string and was instead: ${type(
a,
)}`,
);
}
if (!isNumber(b) && !isString(b)) {
error(
`Failed to normal sort since the second value was not a number or string and was instead: ${type(
b,
)}`,
);
}
if (a < b) {
return -1;
}
if (a > b) {
return 1;
}
return 0;
}
/**
* Helper function to sort an array of objects by one of the object keys.
*
* For example:
*
* ```ts
* const myArray = [
* {
* name: "alice",
* age: 30,
* },
* {
* name: "bob",
* age: 20,
* },
* ];
* myArray.sort(sortObjectArrayByKey("age"));
* ```
*/
export function sortObjectArrayByKey(key: string) {
return (a: unknown, b: unknown): -1 | 0 | 1 => {
if (!isTable(a)) {
error(
`Failed to sort an object array by the key of "${key}" since the first element was not a table and was instead: ${type(
a,
)}`,
);
}
if (!isTable(b)) {
error(
`Failed to sort an object array by the key of "${key}" since the second element was not a table and was instead: ${type(
b,
)}.`,
);
}
const aValue = a.get(key);
const bValue = b.get(key);
return sortNormal(aValue, bValue);
};
}
/**
* Helper function to sort a two-dimensional array by the first element.
*
* For example:
*
* ```ts
* const myArray = [[1, 2], [2, 3], [3, 4]];
* myArray.sort(sortTwoDimensionalArray);
* ```
*
* This function also properly handles when the array elements are strings or numbers (instead of
* another array).
*
* From:
* https://stackoverflow.com/questions/16096872/how-to-sort-2-dimensional-array-by-column-value
*/
export function sortTwoDimensionalArray<T>(
a: readonly T[],
b: readonly T[],
): -1 | 0 | 1 {
const aType = type(a);
const bType = type(b);
if (aType !== bType) {
error(
`Failed to two-dimensional sort since the two elements were disparate types: ${a} & ${b} (${aType} & ${bType})`,
);
}
if (aType === "string" || aType === "number") {
return sortNormal(a, b);
}
if (aType !== "table") {
error(
"Failed to two-dimensional sort since the first element was not a string, number, or table.",
);
}
if (bType !== "table") {
error(
"Failed to two-dimensional sort since the second element was not a string, number, or table.",
);
}
const firstElement1 = a[0];
const firstElement2 = b[0];
if (firstElement1 === undefined || firstElement1 === null) {
error(
"Failed to two-dimensional sort since the first element of the first array was undefined.",
);
}
if (firstElement2 === undefined || firstElement2 === null) {
error(
"Failed to two-dimensional sort since the first element of the second array was undefined.",
);
}
const elementType1 = type(firstElement1);
const elementType2 = type(firstElement2);
if (elementType1 !== elementType2) {
error(
`Failed to two-dimensional sort since the first element of each array were disparate types: ${firstElement1} & ${firstElement2} (${elementType1} & ${elementType2})`,
);
}
return sortNormal(firstElement1, firstElement2);
}
/**
* Helper function to sort an array in a stable way.
*
* This is useful because by default, the transpiled `Array.sort` method from TSTL is not stable.
*
* Under the hood, this uses the merge sort algorithm.
*/
export function stableSort<T>(
// eslint-disable-next-line complete/prefer-readonly-parameter-types
array: T[],
sortFunc: (a: T, b: T) => -1 | 0 | 1 = sortNormal,
// eslint-disable-next-line complete/no-mutable-return
): T[] {
// Base case: an array of zero or one elements is already sorted
if (array.length <= 1) {
return array;
}
// Split the array into two halves.
const middleIndex = Math.floor(array.length / 2);
const leftArray = array.slice(0, middleIndex);
const rightArray = array.slice(middleIndex);
// Recursively sort each half.
const sortedLeftArray = stableSort(leftArray, sortFunc);
const sortedRightArray = stableSort(rightArray, sortFunc);
// Merge the two sorted halves.
const mergedArray: T[] = [];
let leftIndex = 0;
let rightIndex = 0;
while (
leftIndex < sortedLeftArray.length
&& rightIndex < sortedRightArray.length
) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const left = sortedLeftArray[leftIndex]!;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const right = sortedRightArray[rightIndex]!;
const sortResult = sortFunc(left, right);
if (sortResult === -1 || sortResult === 0) {
mergedArray.push(left);
leftIndex++;
} else {
mergedArray.push(right);
rightIndex++;
}
}
// Add any remaining elements from the left array.
while (leftIndex < sortedLeftArray.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const left = sortedLeftArray[leftIndex]!;
mergedArray.push(left);
leftIndex++;
}
// Add any remaining elements from the right array.
while (rightIndex < sortedRightArray.length) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const right = sortedRightArray[rightIndex]!;
mergedArray.push(right);
rightIndex++;
}
return mergedArray;
}