Skip to content

Commit

Permalink
Merge pull request #19 from iamwill123/add/quick-sort
Browse files Browse the repository at this point in the history
add quick sort
  • Loading branch information
iamwill123 authored May 16, 2023
2 parents 7697e76 + 0b36004 commit 3fde8ce
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 26 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,4 @@ yarn coverage

- [ ] Think about separating each algo into its own page
- [ ] Add notes and drawings for each algo
- [ ] Unit the helper functions
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ import nativeSort from './lib/sorts/native'
import selectionSort from './lib/sorts/selection'
import insertionSort from './lib/sorts/insertion'
import mergeSort from './lib/sorts/merge'
import quickSort from './lib/sorts/quick'

export { nativeSort, bubbleSort, selectionSort, insertionSort, mergeSort }
export {
nativeSort,
bubbleSort,
selectionSort,
insertionSort,
mergeSort,
quickSort,
}
34 changes: 33 additions & 1 deletion src/lib/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ArrayInput, GenerateRandomNumbers, SortOrder } from '../../types/sorts'
import {
ArrayInput,
GenerateRandomNumbers,
NumberOrObject,
SortOrder,
} from '../../types/sorts'

const startTime = () => Date.now()
const endTime = () => Date.now()
Expand Down Expand Up @@ -28,6 +33,31 @@ const generateRandomNumbers = ({
const sleep = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms))
}

const pickRandomIndex = (start: number, end: number) =>
Math.floor(Math.random() * (end - start + 1) + start)

// 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.
const compare = (
a: NumberOrObject,
b: NumberOrObject,
key: string = '',
order: string = 'asc'
): number => {
if (key) {
a = a[key]
b = b[key]
}

if (isAsc(order)) {
return a < b ? -1 : a > b ? 1 : 0
} else if (isDesc(order)) {
return a > b ? -1 : a < b ? 1 : 0
} else {
throw new Error(`Invalid order: ${order}.`)
}
}

export {
startTime,
endTime,
Expand All @@ -39,4 +69,6 @@ export {
generateRandomNumbers,
findMaxNumber,
sleep,
pickRandomIndex,
compare,
}
22 changes: 1 addition & 21 deletions src/lib/sorts/insertion/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import {
compare,
endTime,
howLongExecTook,
isAsc,
Expand Down Expand Up @@ -64,25 +65,4 @@ async function insertion(input: SortInput): Promise<SortOutput> {
return { arr, key, order, n, execTime: execTimeInMs, animate }
}

// 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.
function compare(
a: NumberOrObject,
b: NumberOrObject,
key = '',
order = 'asc'
): number {
if (key) {
a = a[key]
b = b[key]
}

if (isAsc(order)) {
return a < b ? -1 : a > b ? 1 : 0
} else if (isDesc(order)) {
return a > b ? -1 : a < b ? 1 : 0
} else {
throw new Error(`Invalid order: ${order}.`)
}
}

export default insertion
146 changes: 146 additions & 0 deletions src/lib/sorts/quick/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
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.
*/

import {
compare,
endTime,
howLongExecTook,
isAnObj,
pickRandomIndex,
startTime,
} from '../../helpers'
import { ArrayInput, SortInput, SortOutput } from '../../../types/sorts'

type HelperInput = {
arr: ArrayInput
startIdx: number
endIdx: number
partitionType: string
order: string
key: string
callback: Function
animate: boolean
}

async function quick_sort(
input: SortInput,
partitionType: string = 'lomuto'
): Promise<SortOutput> {
const _s = startTime()
const {
arr,
order = 'asc',
key = '',
callback = () => {},
isSorting = () => true,
} = input

const n: number = arr.length
let startIdx: number = 0
let endIdx: number = n - 1

let animate: boolean = false

if (n <= 0 || !isSorting()) {
return { arr, key, order, n, execTime: 0, animate: false }
}

if (isAnObj(0, arr) && !key) throw new Error('key is required')

// recursive case
helper({
arr,
startIdx,
endIdx,
partitionType,
order,
key,
callback,
animate,
})

const _e = endTime()
const execTimeInMs = howLongExecTook(_s, _e)

return {
arr,
key,
order,
n,
execTime: execTimeInMs,
animate,
}
}

function helper(helperInput: HelperInput) {
const {
arr,
startIdx,
endIdx,
partitionType,
order,
key,
callback,
animate,
} = helperInput

// base case
// the leaf workers either 1 or 0
if (startIdx >= endIdx) return

let pivotIdx = pickRandomIndex(startIdx, endIdx)
let pivotElem = arr[pivotIdx]
// make the pivot value the start of the array
;[arr[startIdx], arr[pivotIdx]] = [arr[pivotIdx], arr[startIdx]]

let smaller = 0,
bigger = 0

if (partitionType === 'hoare') {
// hoare's partitioning
smaller = startIdx + 1
bigger = endIdx

while (smaller <= bigger) {
// compare the next number at "smaller" to the pivot elem at startIdx
if (compare(arr[smaller], pivotElem, key, order) <= 0) {
smaller++
} else if (compare(arr[bigger], pivotElem, key, order) > 0) {
bigger--
} else {
;[arr[smaller], arr[bigger]] = [arr[bigger], arr[smaller]]
smaller++
bigger--
}
}

// put the pivot element to where the smaller index left off after the loop
;[arr[startIdx], arr[bigger]] = [arr[bigger], arr[startIdx]]

// recursive case
helper({ ...helperInput, endIdx: bigger }) // include the pivot in the first half
helper({ ...helperInput, startIdx: bigger + 1 }) // start from the next element of the pivot
}

if (partitionType === 'lomuto') {
// lomuto's partitioning
smaller = startIdx
bigger = startIdx
for (let i = bigger + 1; i <= endIdx; i++) {
// compare the next number at "bigger" to the pivot elem at startIdx
if (compare(arr[i], pivotElem, key, order) < 0) {
smaller++
;[arr[smaller], arr[i]] = [arr[i], arr[smaller]]
}
}
// put the pivot element to where the smaller index left off after the loop
;[arr[startIdx], arr[smaller]] = [arr[smaller], arr[startIdx]]

// recursive case
helper({ ...helperInput, endIdx: smaller - 1 })
helper({ ...helperInput, startIdx: smaller + 1 })
}
}

export default quick_sort
3 changes: 0 additions & 3 deletions test/sorts/merge/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ describe('merge sort', () => {
let order = 'desc'
const obj = { arr: unsorted().arr.numbers, order }
const { arr: arrOfNums } = await merge(obj)
console.log('🚀 ~ file: index.test.ts:27 ~ test ~ obj:', obj)

console.log('🚀 ~ file: index.test.ts:29 ~ test ~ arrOfNums:', arrOfNums)
expect(arrOfNums).toEqual(sorted({ order }).arr.numbers)
})

Expand Down
104 changes: 104 additions & 0 deletions test/sorts/quick/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import quick from '../../../src/lib/sorts/quick'
import { cases } from '../test-cases'
import { testData } from '../test-data'

describe('quick sort', () => {
const { unsorted, sorted } = testData
test('cases[0]: ' + cases[0], async () => {
const obj = { arr: [5] }
const { arr: arrOfNums } = await quick(obj)
expect(arrOfNums).toEqual([5])

const obj1 = { arr: [] }
const { arr: arrOfNums1 } = await quick(obj1)
expect(arrOfNums1).toEqual([])
})

test('cases[1]: ' + cases[1], async () => {
const obj = { arr: unsorted().arr.numbers }
const { arr: arrOfNums } = await quick(obj)
expect(arrOfNums).toEqual(sorted().arr.numbers)
})

test('cases[2]: ' + cases[2], async () => {
let order = 'desc'
const obj = { arr: unsorted().arr.numbers, order }
const { arr: arrOfNums } = await quick(obj)
expect(arrOfNums).toEqual(sorted({ order }).arr.numbers)
})

test('cases[3]: ' + cases[3], async () => {
const obj = {
arr: unsorted().arr.objects,
key: 'age',
}
const { arr: arrOfObjs } = await quick(obj)

expect(arrOfObjs).toEqual(sorted({}).arr.objects)
})

test('cases[4]: ' + cases[4], async () => {
let key = 'height',
order = 'desc'
const obj = {
arr: unsorted({ key }).arr.objects,
key,
order,
}
const { arr: arrOfObjs } = await quick(obj)
expect(arrOfObjs).toEqual(sorted({ key, order }).arr.objects)
})

test('cases[5]: ' + cases[5], async () => {
const obj = {
arr: unsorted().arr.numbers,
isSorting: () => false,
}
const { arr: arrOfNums } = await quick(obj)
expect(arrOfNums).toEqual(unsorted().arr.numbers)
})

test.todo('cases[6]: ' + cases[6])
// test('cases[6]: ' + cases[6], async () => {
// const obj = {
// arr: unsorted().arr.numbers,
// callback: async (a: number, b: number) => {
// return await Promise.resolve()
// },
// }
// const { arr: arrOfNums, animate } = await quick(obj)
// expect(arrOfNums).toEqual(sorted().arr.numbers)
// expect(animate).toEqual(true)
// })

test.todo('cases[7]: ' + cases[7])
// test('cases[7]: ' + cases[7], async () => {
// const obj = {
// arr: unsorted().arr.numbers,
// callback: async () => {
// return await Promise.resolve()
// },
// }
// const { arr: arrOfNums, animate } = await quick(obj)
// expect(arrOfNums).toEqual(sorted().arr.numbers)
// expect(animate).toEqual(false)
// })
test('cases[8]: should sort correctly when switching partition type to "hoare"', async () => {
const partitionType = 'hoare'
const obj = {
arr: unsorted().arr.numbers,
}
const { arr: arrOfNums } = await quick(obj, partitionType)
expect(arrOfNums).toEqual(sorted().arr.numbers)

let key = 'height',
order = 'desc'
const obj1 = {
arr: unsorted({ key }).arr.objects,
key,
order,
}
const { arr: arrOfObjs } = await quick(obj1, partitionType)
expect(arrOfObjs).toEqual(sorted({ key, order }).arr.objects)
})
})

1 comment on commit 3fde8ce

@vercel
Copy link

@vercel vercel bot commented on 3fde8ce May 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.