Skip to content

Commit

Permalink
feat(sort): implement Tim sort
Browse files Browse the repository at this point in the history
  • Loading branch information
sozelfist committed Apr 15, 2024
1 parent e1f635a commit 79400bb
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 0 deletions.
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,5 @@
* [Selection Sort](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/sorts/selection_sort.ts)
* [Shell Sort](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/sorts/shell_sort.ts)
* [Swap Sort](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/sorts/swap_sort.ts)
* [Tim Sort](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/sorts/tim_sort.ts)
* [Tree Sort](https://github.com/TheAlgorithms/TypeScript/blob/HEAD/sorts/tree_sort.ts)
58 changes: 58 additions & 0 deletions sorts/test/tim_sort.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { timSort } from '../tim_sort'

describe('Tim Sort', () => {
const compareAscending = (a: number, b: number) => a - b
const compareDescending = (a: number, b: number) => b - a

describe('Sorting in increasing order', () => {
it('should return the sorted array for an already sorted array', () => {
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
timSort(arr, compareAscending)
expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
})

it('should return the sorted array for a reverse-sorted array', () => {
const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
timSort(arr, compareAscending)
expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
})

it('should return the sorted array for an average case', () => {
const arr = [1, 4, 2, 5, 9, 6, 3, 8, 10, 7]
timSort(arr, compareAscending)
expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
})

it('should return the sorted array for a best case', () => {
const arr = [1, 4, 2, 9, 5, 7, 3, 8, 10, 6]
timSort(arr, compareAscending)
expect(arr).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
})
})

describe('Sorting in decreasing order', () => {
it('should return the sorted array for an already sorted array', () => {
const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
timSort(arr, compareDescending)
expect(arr).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
})

it('should return the sorted array for a reverse-sorted array', () => {
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
timSort(arr, compareDescending)
expect(arr).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
})

it('should return the sorted array for an average case', () => {
const arr = [1, 4, 2, 5, 9, 6, 3, 8, 10, 7]
timSort(arr, compareDescending)
expect(arr).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
})

it('should return the sorted array for a best case', () => {
const arr = [1, 4, 2, 9, 5, 7, 3, 8, 10, 6]
timSort(arr, compareDescending)
expect(arr).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1])
})
})
})
106 changes: 106 additions & 0 deletions sorts/tim_sort.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Comparator function type definition.
*
* @typeparam T The type of elements being compared.
*/
type Comparator<T> = (a: T, b: T) => number

// Minimum size of subarrays to be sorted using insertion sort before merging
const MIN_MERGE = 32

/**
* Performs insertion sort on a portion of an array.
*
* @typeparam T The type of elements in the array.
* @param arr The array to sort.
* @param start The start index of the portion to sort.
* @param end The end index of the portion to sort.
* @param compare The comparator function defining the order of elements.
*/
const insertionSort = <T>(
arr: T[],
start: number,
end: number,
compare: Comparator<T>
): void => {
for (let i = start + 1; i <= end; i++) {
const key = arr[i]
let j = i - 1

while (j >= start && compare(arr[j], key) > 0) {
arr[j + 1] = arr[j]
j--
}
arr[j + 1] = key
}
}

/**
* Merges two sorted subarrays into one sorted array.
*
* @typeparam T The type of elements in the array.
* @param arr The array containing the subarrays to merge.
* @param leftIndex The left index of the first subarray.
* @param middleIndex The middle index separating the two subarrays.
* @param rightIndex The right index of the second subarray.
* @param compare The comparator function defining the order of elements.
*/
const merge = <T>(
arr: T[],
leftIndex: number,
middleIndex: number,
rightIndex: number,
compare: Comparator<T>
): void => {
const leftArrayLength = middleIndex - leftIndex + 1
const rightArrayLength = rightIndex - middleIndex

const leftArray: T[] = arr.slice(leftIndex, middleIndex + 1)
const rightArray: T[] = arr.slice(middleIndex + 1, rightIndex + 1)

let leftPointer = 0
let rightPointer = 0
let mergedIndex = leftIndex

while (leftPointer < leftArrayLength && rightPointer < rightArrayLength) {
if (compare(leftArray[leftPointer], rightArray[rightPointer]) <= 0) {
arr[mergedIndex++] = leftArray[leftPointer++]
} else {
arr[mergedIndex++] = rightArray[rightPointer++]
}
}

while (leftPointer < leftArrayLength) {
arr[mergedIndex++] = leftArray[leftPointer++]
}

while (rightPointer < rightArrayLength) {
arr[mergedIndex++] = rightArray[rightPointer++]
}
}

/**
* Sorts an array using the Tim sort algorithm.
*
* @typeparam T The type of elements in the array.
* @param arr The array to sort.
* @param compare The comparator function defining the order of elements.
*/
export const timSort = <T>(arr: T[], compare: Comparator<T>): void => {
const n = arr.length

for (let i = 0; i < n; i += MIN_MERGE) {
insertionSort(arr, i, Math.min(i + MIN_MERGE - 1, n - 1), compare)
}

for (let size = MIN_MERGE; size < n; size *= 2) {
for (let left = 0; left < n; left += 2 * size) {
const mid = left + size - 1
const right = Math.min(left + 2 * size - 1, n - 1)

if (mid < right) {
merge(arr, left, mid, right, compare)
}
}
}
}

0 comments on commit 79400bb

Please sign in to comment.