Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timsort branch #910

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Added timesort,test and Readme
gkhedekar5758 committed Jul 5, 2022
commit ca7a11b4cc5aaf5d926c8ece12608f35075eaa0d
37 changes: 37 additions & 0 deletions src/algorithms/sorting/tim-sort/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Timsort


Timsort is a sorting algorithm based on insertion sort and merge sort.
it was invented by Peter Tim in 2002 and this sort is used in the
Java's Array.sort() method as well as Python language's sorted() and sort() method,
in this algorithm first we make the chunks of the array and sort
them using Insertion Sort and then we merge those sorted chunks using
merge of Merge Sort.

The steps are:

1. We divide the array into the blocks of Run
2. Those divided blocks are then sorted using the insertion
sort one by one
3. Once these are sorted we merge the sorted array chunks and combine
them using the merge sort.

The size of the run can be 32 or 64 based on the array size, if those are power of two that
will help us in the performance in the insertion sort

Animated visualization of the timesort algorithm.

![TimSort](https://www.youtube.com/watch?v=NVIjHj-lrT4)

## Complexity

| Name | Best | Average | Worst | Memory | Stable | Comments |
| --------------------- | :-------------: | :-----------------: | :-----------------: | :-------: | :-------: | :-------- |
| **Tim sort** | n O(n) | n n*log(n) | n  n*log(n) | log(n) | Yes | Timsort requires space of O(n) |

## References

- [Wikipedia](https://en.wikipedia.org/wiki/Timsort)
- [YouTube] [Part1](https://www.youtube.com/watch?v=emeME__917E)
[Part2](https://www.youtube.com/watch?v=6DOhQyqAAvU)
[Part3](https://www.youtube.com/watch?v=Yk4CBisILaw)
133 changes: 133 additions & 0 deletions src/algorithms/sorting/tim-sort/TimSort.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import Sort from '../Sort';
// import InsertionSort from '../insertion-sort/InsertionSort';

export default class TimSort extends Sort {
/**
* @name sort
* @description performs sorting using TimSort algorithm
* @param {*[]} originalArray
* @return {*[]}
*/

sort(originalArray) {
const RUN = 32;
// clone the array so that original is intact
// eslint-disable-next-line prefer-const
let array = [...originalArray];

// const lengthOfArray = array.length;

// if array has only one or zero element it is already sorted
if (array.length <= 1) {
return array;
}

// perform insertion sort on the array first
for (let index = 0; index < array.length; index += RUN) {
// this.array=new this.insertionSort().
this.array = this.insertionSort(
array,
index,
this.minValue(index + RUN - 1, array.length - 1),
);
}

// now merge the sorted chunks
for (let size = RUN; size < array.length; size *= 2) {
for (let left = 0; left < array.length; left += 2 * size) {
const mid = left + size - 1;
const right = this.minValue(left + 2 * size - 1, array.length - 1);

if (this.comparator.lessThan(mid, right)) {
this.merge(this.array, left, mid, right);
}
}
}

return this.array;
}

/**
* @description returns a minimum value
* @param {*} a
* @param {*} b
* @returns {*} minimum value of the two
*/
minValue(a, b) {
return this.comparator.lessThan(a, b) ? a : b;
}

/**
* @description merge the sorted array (chunks of array)
* @param {*[]} array
* @param {*} left
* @param {*} mid
* @param {*} right
*/
merge(array, left, mid, right) {
const localArray = [...array];
const leftLength = mid - left + 1;
const rightLength = right - mid;
const leftArray = [];
const rightArray = [];
for (let index = 0; index < leftLength; index += 1) {
leftArray[index] = array[left + index];
}

for (let index = 0; index < rightLength; index += 1) {
rightArray[index] = array[mid + 1 + index];
}

let i = 0;
let j = 0;
let k = left;

while (this.comparator.lessThan(i, leftLength) && this.comparator.lessThan(j, rightLength)) {
if (this.comparator.lessThanOrEqual(leftArray[i], rightArray[j])) {
localArray[k] = leftArray[i];
i += 1;
} else {
localArray[k] = rightArray[j];
}
}
while (this.comparator.lessThan(i, leftLength)) {
localArray[k] = rightArray[i];
k += 1;
i += 1;
}
while (this.comparator.lessThan(j, rightLength)) {
localArray[k] = rightArray[j];
k += 1;
j += 1;
}
// eslint-disable-next-line no-param-reassign
array = Array.from(localArray);
}

/**
* @description perform the insertion sort on the array chunks
* @param {*[]} array
* @param {*} left
* @param {*} right
* @returns {*[]}
*/
insertionSort(array, left, right) {
const localArray = [...array];

for (let index = left + 1; index <= right; index += 1) {
const element = localArray[index];
this.callbacks.visitingCallback(element);
let j = index - 1;
while (this.comparator.greaterThanOrEqual(j, left)
&& this.comparator.greaterThan(localArray[j], element)) {
localArray[j + 1] = localArray[j];
j -= 1;
if (j < 0) break;
}
localArray[j + 1] = element;
}
// eslint-disable-next-line no-param-reassign
array = localArray;
return array;
}
}
64 changes: 64 additions & 0 deletions src/algorithms/sorting/tim-sort/__test__/TimSort.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import TimSort from '../TimSort';
import {
equalArr,
notSortedArr,
reverseArr,
sortedArr,
SortTester,
} from '../../SortTester';

// Complexity constants.
const SORTED_ARRAY_VISITING_COUNT = 19;
const NOT_SORTED_ARRAY_VISITING_COUNT = 19;
const REVERSE_SORTED_ARRAY_VISITING_COUNT = 19;
const EQUAL_ARRAY_VISITING_COUNT = 19;

describe('TimSort', () => {
it('should sort array', () => {
SortTester.testSort(TimSort);
});

it('should sort array with custom comparator', () => {
SortTester.testSortWithCustomComparator(TimSort);
});

it('should do stable sorting', () => {
SortTester.testSortStability(TimSort);
});

it('should sort negative numbers', () => {
SortTester.testNegativeNumbersSort(TimSort);
});

it('should visit EQUAL array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
TimSort,
equalArr,
EQUAL_ARRAY_VISITING_COUNT,
);
});

it('should visit SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
TimSort,
sortedArr,
SORTED_ARRAY_VISITING_COUNT,
);
});

it('should visit NOT SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
TimSort,
notSortedArr,
NOT_SORTED_ARRAY_VISITING_COUNT,
);
});

it('should visit REVERSE SORTED array element specified number of times', () => {
SortTester.testAlgorithmTimeComplexity(
TimSort,
reverseArr,
REVERSE_SORTED_ARRAY_VISITING_COUNT,
);
});
});