From ae8e4d317480ce07408f3fcc0448420f64efff92 Mon Sep 17 00:00:00 2001 From: HamsterCoder Date: Fri, 29 Sep 2023 21:43:36 +0200 Subject: [PATCH] Implement QuickSort and tests. --- README.md | 9 +++-- package.json | 2 +- src/quickSort.test.ts | 85 +++++++++++++++++++++++++++++++++++++++++++ src/quickSort.ts | 52 ++++++++++++++++++++++++++ src/shellSort.test.ts | 2 +- src/shellSort.ts | 4 +- src/shuffle.ts | 26 +++++++------ 7 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 src/quickSort.test.ts create mode 100644 src/quickSort.ts diff --git a/README.md b/README.md index 988629d..416a89c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ ## About -This repository contains implementations of some data structures and algorithms. It serves the purpose of reference. Probable not production ready. +This repository contains implementations of some data structures and algorithms. It serves the purpose of reference. Not for production use. -### What do you have +### What do you have? -* Shell Sort -* Knuth Shuffle +* Knuth Shuffle - returns a shuffled copy of the array +* Shell Sort - returns a sorted copy of the array +* Quick Sort - returns a sorted copy of the array ## Development diff --git a/package.json b/package.json index 7e1ab7d..577321c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Implementation of some data structures and algorithms", "main": "index.js", "scripts": { - "test": "jest" + "test": "jest --verbose" }, "author": "", "license": "MIT", diff --git a/src/quickSort.test.ts b/src/quickSort.test.ts new file mode 100644 index 0000000..c5e5ea5 --- /dev/null +++ b/src/quickSort.test.ts @@ -0,0 +1,85 @@ +import { quickSort, partition } from "./quickSort"; +import { shuffle } from "./shuffle"; + +function lessThan(a: number, b: number): boolean { + return a < b; +} + +function debugSort(array: number[]) { + let result = quickSort(array, lessThan); + + // console.log(array, result); + + return result; +} + +const TEST_ARRAY = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]; + +function isPartitioned(array: T[], lessThan: (a: T, b: T) => boolean, lt: number, gt: number): boolean { + for (let i = 0; i < array.length; i += 1) { + + if (i < lt) { + if (!lessThan(array[i], array[lt])) { + return false; + } + } else if (i > gt) { + if (!lessThan(array[gt], array[i])) { + return false; + } + } else { + if ( + lessThan(array[i], array[lt]) || + lessThan(array[lt], array[i]) || + lessThan(array[i], array[gt]) || + lessThan(array[gt], array[i]) + ) { + return false; + } + } + } + + return true; +} + +describe('quickSort', () => { + describe('partition', () => { + test('Correct boundaries, correctly partitioned 1', () => { + let TEST_PARTITION = [4, 3, 1, 5, 4, 7, 6, 4, 2]; + let res = partition(TEST_PARTITION, lessThan, 0, TEST_PARTITION.length - 1); + expect(res).toEqual([3, 5]); + expect(isPartitioned(TEST_PARTITION, lessThan, ...res)).toEqual(true); + }); + + test('Correct boundaries, correctly partitioned 2', () => { + let TEST_PARTITION = [2, 3, 1, 5, 4, 7, 6, 4, 2, 2]; + let res = partition(TEST_PARTITION, lessThan, 0, TEST_PARTITION.length - 1); + expect(res).toEqual([1, 3]); + expect(isPartitioned(TEST_PARTITION, lessThan, ...res)).toEqual(true); + }); + }); + + test('Returns a copy', () => { + expect(debugSort(TEST_ARRAY)).not.toBe(TEST_ARRAY); + }); + + test('Sorts a sorted array', () => { + expect(debugSort(TEST_ARRAY)).toEqual(TEST_ARRAY); + }); + + test('Sorts a reversed array', () => { + expect(debugSort(TEST_ARRAY.slice().reverse())).toEqual(TEST_ARRAY); + }); + + test('Sorts a shuffled array: attempt 1', () => { + expect(debugSort(shuffle(TEST_ARRAY))).toEqual(TEST_ARRAY); + }); + + test('Sorts a shuffled array: attempt 2', () => { + expect(debugSort(shuffle(TEST_ARRAY))).toEqual(TEST_ARRAY); + }); + + test('Sorts a shuffled array: attempt 3', () => { + expect(debugSort(shuffle(TEST_ARRAY))).toEqual(TEST_ARRAY); + }); +}); + diff --git a/src/quickSort.ts b/src/quickSort.ts new file mode 100644 index 0000000..38b2b8b --- /dev/null +++ b/src/quickSort.ts @@ -0,0 +1,52 @@ +import { shuffleInplace } from "./shuffle"; + +// 3-way partition implementation +export function partition(array: T[], lessThan: (a: T, b: T) => boolean, i: number, j: number): [number, number] { + let value = array[i]; + let lt = i; + let gt = j; + let index = i + 1; + + while (index <= gt) { + if (lessThan(array[index], value)) { + let temp = array[lt]; + array[lt] = array[index]; + array[index] = temp; + index += 1; + lt += 1; + } else if (lessThan(value, array[index])) { + let temp = array[gt]; + array[gt] = array[index]; + array[index] = temp; + gt -= 1; + } else { + index += 1; + } + } + + return [lt, gt]; +} + +function sort(array: T[], lessThan: (a: T, b: T) => boolean, i: number, j: number): T[] { + if (i >= j) { + return array; + } + + // 1. Shuffle + array = shuffleInplace(array, i, j); + + // 2. Partition + let [lt, gt] = partition(array,lessThan, i, j); + + // 3. Sort recursively + array = sort(array, lessThan, i, lt - 1); + array = sort(array, lessThan, gt + 1, j); + + return array; +} + +export const quickSort = function (array: T[], lessThan: (a: T, b: T) => boolean): T[] { + let sortedArray = array.slice(); + + return sort(sortedArray, lessThan, 0, sortedArray.length - 1); +}; \ No newline at end of file diff --git a/src/shellSort.test.ts b/src/shellSort.test.ts index c11fa38..2c1dc21 100644 --- a/src/shellSort.test.ts +++ b/src/shellSort.test.ts @@ -3,7 +3,7 @@ import { shuffle } from "./shuffle"; const TEST_ARRAY = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -function lessThan(a: number, b: number) { +function lessThan(a: number, b: number): boolean { return a < b; } diff --git a/src/shellSort.ts b/src/shellSort.ts index 26f31c4..fc1225c 100644 --- a/src/shellSort.ts +++ b/src/shellSort.ts @@ -8,9 +8,7 @@ export const shellSort = function (array: T[], lessThan: (a: T, b: T) => bool h = 3 * h + 1; } - while (h >= 1) { - console.log(`h: ${h}`); - + while (h >= 1) { for (let i = 0; i < n - h; i += 1) { for (let j = i; j < n; j += h) { let k = j; diff --git a/src/shuffle.ts b/src/shuffle.ts index 20f7afc..b001941 100644 --- a/src/shuffle.ts +++ b/src/shuffle.ts @@ -1,22 +1,26 @@ -// Implementation of Knuth Shuffle Algorithm export function shuffle(array: T[]) { - let currentIndex = array.length, - randomIndex; - let shuffledArray = array.slice(); + return shuffleInplace(shuffledArray, 0, shuffledArray.length - 1); +} + +// Implementation of Knuth Shuffle Algorithm +export function shuffleInplace(array: T[], i: number, j: number) { + let currentIndex = j, + randomIndex; + // While there remain elements to shuffle. - while (currentIndex != 0) { + while (currentIndex !== i) { // Pick a remaining element. - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; + randomIndex = Math.floor(Math.random() * (currentIndex - i) + i); + currentIndex -= 1; // And swap it with the current element. - [shuffledArray[currentIndex], shuffledArray[randomIndex]] = [ - shuffledArray[randomIndex], - shuffledArray[currentIndex], + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], + array[currentIndex], ]; } - return shuffledArray; + return array; }