Skip to content

Commit 3fde8ce

Browse files
authored
Merge pull request #19 from iamwill123/add/quick-sort
add quick sort
2 parents 7697e76 + 0b36004 commit 3fde8ce

File tree

7 files changed

+294
-26
lines changed

7 files changed

+294
-26
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ yarn coverage
108108

109109
- [ ] Think about separating each algo into its own page
110110
- [ ] Add notes and drawings for each algo
111+
- [ ] Unit the helper functions

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,13 @@ import nativeSort from './lib/sorts/native'
33
import selectionSort from './lib/sorts/selection'
44
import insertionSort from './lib/sorts/insertion'
55
import mergeSort from './lib/sorts/merge'
6+
import quickSort from './lib/sorts/quick'
67

7-
export { nativeSort, bubbleSort, selectionSort, insertionSort, mergeSort }
8+
export {
9+
nativeSort,
10+
bubbleSort,
11+
selectionSort,
12+
insertionSort,
13+
mergeSort,
14+
quickSort,
15+
}

src/lib/helpers/index.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ArrayInput, GenerateRandomNumbers, SortOrder } from '../../types/sorts'
1+
import {
2+
ArrayInput,
3+
GenerateRandomNumbers,
4+
NumberOrObject,
5+
SortOrder,
6+
} from '../../types/sorts'
27

38
const startTime = () => Date.now()
49
const endTime = () => Date.now()
@@ -28,6 +33,31 @@ const generateRandomNumbers = ({
2833
const sleep = (ms: number): Promise<void> => {
2934
return new Promise((resolve) => setTimeout(resolve, ms))
3035
}
36+
37+
const pickRandomIndex = (start: number, end: number) =>
38+
Math.floor(Math.random() * (end - start + 1) + start)
39+
40+
// compare is a helper function that compares two numbers or two objects by a key and order (asc or desc) and returns a number (-1, 0, or 1) based on the comparison.
41+
const compare = (
42+
a: NumberOrObject,
43+
b: NumberOrObject,
44+
key: string = '',
45+
order: string = 'asc'
46+
): number => {
47+
if (key) {
48+
a = a[key]
49+
b = b[key]
50+
}
51+
52+
if (isAsc(order)) {
53+
return a < b ? -1 : a > b ? 1 : 0
54+
} else if (isDesc(order)) {
55+
return a > b ? -1 : a < b ? 1 : 0
56+
} else {
57+
throw new Error(`Invalid order: ${order}.`)
58+
}
59+
}
60+
3161
export {
3262
startTime,
3363
endTime,
@@ -39,4 +69,6 @@ export {
3969
generateRandomNumbers,
4070
findMaxNumber,
4171
sleep,
72+
pickRandomIndex,
73+
compare,
4274
}

src/lib/sorts/insertion/index.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
import {
13+
compare,
1314
endTime,
1415
howLongExecTook,
1516
isAsc,
@@ -64,25 +65,4 @@ async function insertion(input: SortInput): Promise<SortOutput> {
6465
return { arr, key, order, n, execTime: execTimeInMs, animate }
6566
}
6667

67-
// compare is a helper function that compares two numbers or two objects by a key and order (asc or desc) and returns a number (-1, 0, or 1) based on the comparison.
68-
function compare(
69-
a: NumberOrObject,
70-
b: NumberOrObject,
71-
key = '',
72-
order = 'asc'
73-
): number {
74-
if (key) {
75-
a = a[key]
76-
b = b[key]
77-
}
78-
79-
if (isAsc(order)) {
80-
return a < b ? -1 : a > b ? 1 : 0
81-
} else if (isDesc(order)) {
82-
return a > b ? -1 : a < b ? 1 : 0
83-
} else {
84-
throw new Error(`Invalid order: ${order}.`)
85-
}
86-
}
87-
8868
export default insertion

src/lib/sorts/quick/index.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
quick sort is a divide and conquer algorithm that recursively divides a list into a smaller list and a larger list based on a pivot element. It then sorts the smaller list and the larger list by recursively calling itself. This process continues until the entire list is sorted. Here we are using the Lomuto partition scheme.
3+
*/
4+
5+
import {
6+
compare,
7+
endTime,
8+
howLongExecTook,
9+
isAnObj,
10+
pickRandomIndex,
11+
startTime,
12+
} from '../../helpers'
13+
import { ArrayInput, SortInput, SortOutput } from '../../../types/sorts'
14+
15+
type HelperInput = {
16+
arr: ArrayInput
17+
startIdx: number
18+
endIdx: number
19+
partitionType: string
20+
order: string
21+
key: string
22+
callback: Function
23+
animate: boolean
24+
}
25+
26+
async function quick_sort(
27+
input: SortInput,
28+
partitionType: string = 'lomuto'
29+
): Promise<SortOutput> {
30+
const _s = startTime()
31+
const {
32+
arr,
33+
order = 'asc',
34+
key = '',
35+
callback = () => {},
36+
isSorting = () => true,
37+
} = input
38+
39+
const n: number = arr.length
40+
let startIdx: number = 0
41+
let endIdx: number = n - 1
42+
43+
let animate: boolean = false
44+
45+
if (n <= 0 || !isSorting()) {
46+
return { arr, key, order, n, execTime: 0, animate: false }
47+
}
48+
49+
if (isAnObj(0, arr) && !key) throw new Error('key is required')
50+
51+
// recursive case
52+
helper({
53+
arr,
54+
startIdx,
55+
endIdx,
56+
partitionType,
57+
order,
58+
key,
59+
callback,
60+
animate,
61+
})
62+
63+
const _e = endTime()
64+
const execTimeInMs = howLongExecTook(_s, _e)
65+
66+
return {
67+
arr,
68+
key,
69+
order,
70+
n,
71+
execTime: execTimeInMs,
72+
animate,
73+
}
74+
}
75+
76+
function helper(helperInput: HelperInput) {
77+
const {
78+
arr,
79+
startIdx,
80+
endIdx,
81+
partitionType,
82+
order,
83+
key,
84+
callback,
85+
animate,
86+
} = helperInput
87+
88+
// base case
89+
// the leaf workers either 1 or 0
90+
if (startIdx >= endIdx) return
91+
92+
let pivotIdx = pickRandomIndex(startIdx, endIdx)
93+
let pivotElem = arr[pivotIdx]
94+
// make the pivot value the start of the array
95+
;[arr[startIdx], arr[pivotIdx]] = [arr[pivotIdx], arr[startIdx]]
96+
97+
let smaller = 0,
98+
bigger = 0
99+
100+
if (partitionType === 'hoare') {
101+
// hoare's partitioning
102+
smaller = startIdx + 1
103+
bigger = endIdx
104+
105+
while (smaller <= bigger) {
106+
// compare the next number at "smaller" to the pivot elem at startIdx
107+
if (compare(arr[smaller], pivotElem, key, order) <= 0) {
108+
smaller++
109+
} else if (compare(arr[bigger], pivotElem, key, order) > 0) {
110+
bigger--
111+
} else {
112+
;[arr[smaller], arr[bigger]] = [arr[bigger], arr[smaller]]
113+
smaller++
114+
bigger--
115+
}
116+
}
117+
118+
// put the pivot element to where the smaller index left off after the loop
119+
;[arr[startIdx], arr[bigger]] = [arr[bigger], arr[startIdx]]
120+
121+
// recursive case
122+
helper({ ...helperInput, endIdx: bigger }) // include the pivot in the first half
123+
helper({ ...helperInput, startIdx: bigger + 1 }) // start from the next element of the pivot
124+
}
125+
126+
if (partitionType === 'lomuto') {
127+
// lomuto's partitioning
128+
smaller = startIdx
129+
bigger = startIdx
130+
for (let i = bigger + 1; i <= endIdx; i++) {
131+
// compare the next number at "bigger" to the pivot elem at startIdx
132+
if (compare(arr[i], pivotElem, key, order) < 0) {
133+
smaller++
134+
;[arr[smaller], arr[i]] = [arr[i], arr[smaller]]
135+
}
136+
}
137+
// put the pivot element to where the smaller index left off after the loop
138+
;[arr[startIdx], arr[smaller]] = [arr[smaller], arr[startIdx]]
139+
140+
// recursive case
141+
helper({ ...helperInput, endIdx: smaller - 1 })
142+
helper({ ...helperInput, startIdx: smaller + 1 })
143+
}
144+
}
145+
146+
export default quick_sort

test/sorts/merge/index.test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ describe('merge sort', () => {
2424
let order = 'desc'
2525
const obj = { arr: unsorted().arr.numbers, order }
2626
const { arr: arrOfNums } = await merge(obj)
27-
console.log('🚀 ~ file: index.test.ts:27 ~ test ~ obj:', obj)
28-
29-
console.log('🚀 ~ file: index.test.ts:29 ~ test ~ arrOfNums:', arrOfNums)
3027
expect(arrOfNums).toEqual(sorted({ order }).arr.numbers)
3128
})
3229

test/sorts/quick/index.test.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import quick from '../../../src/lib/sorts/quick'
2+
import { cases } from '../test-cases'
3+
import { testData } from '../test-data'
4+
5+
describe('quick sort', () => {
6+
const { unsorted, sorted } = testData
7+
test('cases[0]: ' + cases[0], async () => {
8+
const obj = { arr: [5] }
9+
const { arr: arrOfNums } = await quick(obj)
10+
expect(arrOfNums).toEqual([5])
11+
12+
const obj1 = { arr: [] }
13+
const { arr: arrOfNums1 } = await quick(obj1)
14+
expect(arrOfNums1).toEqual([])
15+
})
16+
17+
test('cases[1]: ' + cases[1], async () => {
18+
const obj = { arr: unsorted().arr.numbers }
19+
const { arr: arrOfNums } = await quick(obj)
20+
expect(arrOfNums).toEqual(sorted().arr.numbers)
21+
})
22+
23+
test('cases[2]: ' + cases[2], async () => {
24+
let order = 'desc'
25+
const obj = { arr: unsorted().arr.numbers, order }
26+
const { arr: arrOfNums } = await quick(obj)
27+
expect(arrOfNums).toEqual(sorted({ order }).arr.numbers)
28+
})
29+
30+
test('cases[3]: ' + cases[3], async () => {
31+
const obj = {
32+
arr: unsorted().arr.objects,
33+
key: 'age',
34+
}
35+
const { arr: arrOfObjs } = await quick(obj)
36+
37+
expect(arrOfObjs).toEqual(sorted({}).arr.objects)
38+
})
39+
40+
test('cases[4]: ' + cases[4], async () => {
41+
let key = 'height',
42+
order = 'desc'
43+
const obj = {
44+
arr: unsorted({ key }).arr.objects,
45+
key,
46+
order,
47+
}
48+
const { arr: arrOfObjs } = await quick(obj)
49+
expect(arrOfObjs).toEqual(sorted({ key, order }).arr.objects)
50+
})
51+
52+
test('cases[5]: ' + cases[5], async () => {
53+
const obj = {
54+
arr: unsorted().arr.numbers,
55+
isSorting: () => false,
56+
}
57+
const { arr: arrOfNums } = await quick(obj)
58+
expect(arrOfNums).toEqual(unsorted().arr.numbers)
59+
})
60+
61+
test.todo('cases[6]: ' + cases[6])
62+
// test('cases[6]: ' + cases[6], async () => {
63+
// const obj = {
64+
// arr: unsorted().arr.numbers,
65+
// callback: async (a: number, b: number) => {
66+
// return await Promise.resolve()
67+
// },
68+
// }
69+
// const { arr: arrOfNums, animate } = await quick(obj)
70+
// expect(arrOfNums).toEqual(sorted().arr.numbers)
71+
// expect(animate).toEqual(true)
72+
// })
73+
74+
test.todo('cases[7]: ' + cases[7])
75+
// test('cases[7]: ' + cases[7], async () => {
76+
// const obj = {
77+
// arr: unsorted().arr.numbers,
78+
// callback: async () => {
79+
// return await Promise.resolve()
80+
// },
81+
// }
82+
// const { arr: arrOfNums, animate } = await quick(obj)
83+
// expect(arrOfNums).toEqual(sorted().arr.numbers)
84+
// expect(animate).toEqual(false)
85+
// })
86+
test('cases[8]: should sort correctly when switching partition type to "hoare"', async () => {
87+
const partitionType = 'hoare'
88+
const obj = {
89+
arr: unsorted().arr.numbers,
90+
}
91+
const { arr: arrOfNums } = await quick(obj, partitionType)
92+
expect(arrOfNums).toEqual(sorted().arr.numbers)
93+
94+
let key = 'height',
95+
order = 'desc'
96+
const obj1 = {
97+
arr: unsorted({ key }).arr.objects,
98+
key,
99+
order,
100+
}
101+
const { arr: arrOfObjs } = await quick(obj1, partitionType)
102+
expect(arrOfObjs).toEqual(sorted({ key, order }).arr.objects)
103+
})
104+
})

0 commit comments

Comments
 (0)