-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #79 from molgenis/fix/sortNullValues
fix:sort null values
- Loading branch information
Showing
5 changed files
with
192 additions
and
73 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { describe, expect, test } from "vitest"; | ||
import { compareAsc, compareDesc } from "../compare"; | ||
|
||
describe("compare functions", () => { | ||
test("sort strings ascending", () => { | ||
expect(["b", null, "A", "c", "b"].sort(compareAsc)).toStrictEqual(["A", "b", "b", "c", null]); | ||
}); | ||
|
||
test("sort strings descending", () => { | ||
expect(["b", null, null, "A", "c"].sort(compareDesc)).toStrictEqual(["c", "b", "A", null, null]); | ||
}); | ||
|
||
test("sort numbers ascending", () => { | ||
expect([2, null, 1, 3].sort(compareAsc)).toStrictEqual([1, 2, 3, null]); | ||
}); | ||
|
||
test("sort numbers descending", () => { | ||
expect([null, 2, 1, 1, 3].sort(compareDesc)).toStrictEqual([3, 2, 1, 1, null]); | ||
}); | ||
|
||
test("sort boolean ascending", () => { | ||
expect([true, null, true, false].sort(compareAsc)).toStrictEqual([true, true, false, null]); | ||
}); | ||
|
||
test("sort boolean descending", () => { | ||
expect([true, null, false].sort(compareDesc)).toStrictEqual([false, true, null]); | ||
}); | ||
|
||
test("sort array ascending", () => { | ||
const array0 = [3, 2, 4]; | ||
const array1 = [2, null, 1, 3]; | ||
const array2: string[] = []; | ||
const array3: string[] = []; | ||
const array4 = [null, 4, 5, 6]; | ||
const array5 = [null]; | ||
expect([array0, array1, array2, array3, array4, array5].sort(compareAsc)).toStrictEqual([ | ||
array1, | ||
array0, | ||
array4, | ||
array5, | ||
array2, | ||
array3, | ||
]); | ||
}); | ||
|
||
test("sort array descending", () => { | ||
const array0 = [3, 2, 4]; | ||
const array1 = [2, null, 1, 3]; | ||
const array2: string[] = []; | ||
const array3: string[] = []; | ||
const array4 = [null, 4, 5, 6]; | ||
const array5 = [null]; | ||
expect([array0, array1, array2, array3, array4, array5].sort(compareDesc)).toStrictEqual([ | ||
array4, | ||
array0, | ||
array1, | ||
array5, | ||
array2, | ||
array3, | ||
]); | ||
}); | ||
|
||
test("sort arrays throws an error", () => { | ||
expect(() => [[{ x: 0, y: 1 }], [{ x: 1, y: 2 }]].sort(compareAsc)).toThrowError( | ||
"can't compare values of type 'object'." | ||
); | ||
}); | ||
|
||
test("sort object throws an error", () => { | ||
expect(() => | ||
[ | ||
{ x: 0, y: 1 }, | ||
{ x: 1, y: 2 }, | ||
].sort(compareAsc) | ||
).toThrowError("can't compare values of type 'object'."); | ||
}); | ||
}); |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
class CompareTypeError extends Error { | ||
constructor(value: unknown) { | ||
super(`can't compare values of type '${typeof value}'.`); | ||
this.name = "CompareTypeError"; | ||
} | ||
} | ||
|
||
export function compareDesc(a: unknown, b: unknown) { | ||
if (a === null) { | ||
return b === null ? 0 : 1; | ||
} else if (b === null) { | ||
return -1; | ||
} else if (Array.isArray(a) && Array.isArray(b)) { | ||
return compareDescArray(a, b); | ||
} else { | ||
return compareAscNonNullPrimitives(b, a); | ||
} | ||
} | ||
|
||
export function compareAsc(a: unknown, b: unknown): number { | ||
if (a === null) { | ||
return b === null ? 0 : 1; | ||
} else if (b === null) { | ||
return -1; | ||
} else if (Array.isArray(a) && Array.isArray(b)) { | ||
return compareAscArray(a, b); | ||
} else { | ||
return compareAscNonNullPrimitives(a, b); | ||
} | ||
} | ||
|
||
function compareAscNonNullPrimitives(a: unknown, b: unknown): number { | ||
if (typeof a === "number" && typeof b === "number") { | ||
return compareAscNumber(a, b); | ||
} else if (typeof a === "string" && typeof b === "string") { | ||
return compareAscString(a, b); | ||
} else if (typeof a === "boolean" && typeof b === "boolean") { | ||
return compareAscBoolean(a, b); | ||
} else { | ||
throw new CompareTypeError(a); | ||
} | ||
} | ||
|
||
function compareAscNumber(a: number, b: number): number { | ||
return a - b; | ||
} | ||
|
||
function compareAscString(a: string, b: string): number { | ||
return a.toUpperCase().localeCompare(b.toUpperCase()); | ||
} | ||
|
||
function compareAscBoolean(a: boolean, b: boolean): number { | ||
if (a === b) { | ||
return 0; | ||
} else { | ||
return a ? -1 : 1; | ||
} | ||
} | ||
|
||
function checkArrays(arrays: unknown[][]) { | ||
for (const array of arrays) { | ||
const firstA = array.find((value) => value !== null); | ||
if (firstA !== undefined && typeof firstA !== "number" && typeof firstA !== "string") | ||
throw new CompareTypeError(firstA); | ||
} | ||
} | ||
|
||
function compareAscArray(a: unknown[], b: unknown[]): number { | ||
if (a.length === 0) { | ||
return b.length === 0 ? 0 : 1; | ||
} else if (b.length === 0) { | ||
return -1; | ||
} else { | ||
checkArrays([a, b]); | ||
|
||
// sort on first array item | ||
const sortedA = a.slice().sort(compareAsc); | ||
const sortedB = b.slice().sort(compareAsc); | ||
return compareAsc(sortedA[0], sortedB[0]); | ||
} | ||
} | ||
|
||
function compareDescArray(a: unknown[], b: unknown[]): number { | ||
if (a.length === 0) { | ||
return b.length === 0 ? 0 : 1; | ||
} else if (b.length === 0) { | ||
return -1; | ||
} else { | ||
checkArrays([a, b]); | ||
|
||
// sort on first array item | ||
const sortedA = a.slice().sort(compareDesc); | ||
const sortedB = b.slice().sort(compareDesc); | ||
return compareDesc(sortedA[0], sortedB[0]); | ||
} | ||
} |