├── .gitignore
├── README.md
├── docs
└── _config.yml
├── package-lock.json
├── package.json
├── public
├── favicon.png
├── index.html
├── manifest.json
└── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── setupTests.js
└── sortingvisualizer
├── SortingVisualizer.css
├── SortingVisualizer.jsx
├── algorithms.js
└── sortingAlgorithms.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React App to visualize Sorting Algorithms
2 |
3 | This app is a visual way to see merge sort, bubble sort, insertion sort and quick sort sorting algorithms in action. Available at: https://tuomaskivioja.github.io/SortingAlgorithmsVisualized/
4 |
5 | Idea inspired by Clement Mihailescu.
6 |
7 | ## Implementation
8 |
9 | By clicking 'genrate array' the user generates a number array of length 250 which is stored in the SortingVisualizer object's state. This is then translated into array bars by loopin gover the array and giving each bar a height of 0.1% times the value of the array.
10 |
11 | By clicking on a button for any given algorithm, the user can see the array bars being sorted where the height of the bar represents a value in an array of numbers.
12 |
13 | ## The Algorithms
14 |
15 | I have implemented the algorithms using numbers only in algorithms.js. Then, in sortingAlgorithms.js I have translated these into animations that I pass into the relevant algorithms inside SortingVisualizer.jsx which actually translates these animations into height values for the array bars in the visualizer.
16 |
17 | ## The Future
18 |
19 | In the future, I will be implementing a toggle where the user can switch between a single algorithm view to a comparison window where multiple algorithms can be run in parallel.
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "https://tuomaskivioja.github.io/SortingAlgorithmsVisualized",
3 | "name": "sorting-visualizer",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "@testing-library/jest-dom": "^5.16.2",
8 | "@testing-library/react": "^12.1.3",
9 | "@testing-library/user-event": "^13.5.0",
10 | "gh-pages": "^3.2.3",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-scripts": "5.0.0",
14 | "web-vitals": "^2.1.4"
15 | },
16 | "scripts": {
17 | "predeploy": "npm run build",
18 | "deploy": "gh-pages -d build",
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": [
26 | "react-app",
27 | "react-app/jest"
28 | ]
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuomaskivioja/SortingAlgorithmsVisualized/8918e58ff48be851dc833a29272208cf28cf8a70/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
25 | Sorting Visualizer
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import SortingVisualizer from './sortingvisualizer/SortingVisualizer';
3 | import './App.css';
4 |
5 | function App() {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/sortingvisualizer/SortingVisualizer.css:
--------------------------------------------------------------------------------
1 | .app {
2 | width: 100vw;
3 | height: 100vh;
4 | }
5 |
6 | .navbar {
7 | height: 20%;
8 | width: 100%;
9 | display: flex;
10 | flex-direction: row;
11 | justify-content: space-around;
12 | align-items: center;
13 | background-color:midnightblue;
14 | }
15 |
16 | .array-container {
17 | width: 100%;
18 | height: 80%;
19 | display: flex;
20 | flex-direction: row;
21 | justify-content: center;
22 | align-items: center;
23 | }
24 | .array-bars-container {
25 | width: 85%;
26 | height: 80%;
27 | }
28 | .array-bar {
29 | width: 0.2%;
30 | display: inline-block;
31 | margin-left: 0.1%;
32 | margin-right: 0.1%;
33 | }
34 | .timeBox {
35 | font-size: 2em;
36 | }
--------------------------------------------------------------------------------
/src/sortingvisualizer/SortingVisualizer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './SortingVisualizer.css';
3 | import {getMergeSortAnimations, getBubbleSortAnimations, getSelectionSortAnimations, getQuickSortAnimations} from './sortingAlgorithms.js';
4 |
5 | // Change this value for the speed of the animations.
6 | const ANIMATION_SPEED_MS = 1;
7 |
8 | // Change this value for the number of bars (value) in the array.
9 | const NUMBER_OF_ARRAY_BARS = 250;
10 |
11 | // This is the main color of the array bars.
12 | const PRIMARY_COLOR = 'turquoise';
13 |
14 | // This is the color of array bars that are being compared throughout the animations.
15 | const SECONDARY_COLOR = 'red';
16 |
17 | function randomInteger(min, max) {
18 | return Math.floor(Math.random() * (max - min + 1)) + min;
19 | }
20 |
21 | export default class SortingVisualizer extends React.Component {
22 | constructor(props) {
23 | super(props);
24 |
25 | this.state = {
26 | array: [],
27 | };
28 | }
29 |
30 | componentDidMount() {
31 | this.resetArray();
32 | }
33 |
34 | runSort(animations) {
35 | for (let i = 0; i < animations.length; i++) {
36 | const arrayBars = document.getElementsByClassName('array-bar');
37 | const isColorChange = i % 3 !== 2;
38 | if (isColorChange) {
39 | const [barOneIdx, barTwoIdx] = animations[i];
40 | const barOneStyle = arrayBars[barOneIdx].style;
41 | const barTwoStyle = arrayBars[barTwoIdx].style;
42 | const color = i % 3 === 0 ? SECONDARY_COLOR : PRIMARY_COLOR;
43 | setTimeout(() => {
44 | barOneStyle.backgroundColor = color;
45 | barTwoStyle.backgroundColor = color;
46 | }, i * ANIMATION_SPEED_MS);
47 | }
48 |
49 | else if (animations[i] === "noSwap") {
50 | setTimeout(() => {
51 | }, i * ANIMATION_SPEED_MS);
52 | }
53 |
54 | else {
55 | setTimeout(() => {
56 | const [barOneIdx, newHeight1, barTwoIdx, newHeight2] = animations[i];
57 | const barOneStyle = arrayBars[barOneIdx].style;
58 | barOneStyle.height = `${newHeight1 * 0.1}%`;
59 | const barTwoStyle = arrayBars[barTwoIdx].style;
60 | barTwoStyle.height = `${newHeight2 * 0.1}%`;
61 | }, i * ANIMATION_SPEED_MS);
62 | }
63 | }
64 | }
65 |
66 | resetArray() {
67 | const array = []
68 | for (let i = 0; i < NUMBER_OF_ARRAY_BARS; i++) {
69 | array.push(randomInteger(5, 1000))
70 | }
71 | this.setState({array});
72 | }
73 |
74 | mergeSort() {
75 | const animations = getMergeSortAnimations(this.state.array);
76 | for (let i = 0; i < animations.length; i++) {
77 | const arrayBars = document.getElementsByClassName('array-bar');
78 | const isColorChange = i % 3 !== 2;
79 | if (isColorChange) {
80 | const [barOneIdx, barTwoIdx] = animations[i];
81 | const barOneStyle = arrayBars[barOneIdx].style;
82 | const barTwoStyle = arrayBars[barTwoIdx].style;
83 | const color = i % 3 === 0 ? SECONDARY_COLOR : PRIMARY_COLOR;
84 | setTimeout(() => {
85 | barOneStyle.backgroundColor = color;
86 | barTwoStyle.backgroundColor = color;
87 | }, i * ANIMATION_SPEED_MS);
88 | } else {
89 | setTimeout(() => {
90 | const [barOneIdx, newHeight] = animations[i];
91 | const barOneStyle = arrayBars[barOneIdx].style;
92 | barOneStyle.height = `${newHeight * 0.1}%`;
93 | }, i * ANIMATION_SPEED_MS);
94 | }
95 | }
96 | }
97 |
98 | quickSort() {
99 | const animations = getQuickSortAnimations(this.state.array);
100 | this.runSort(animations)
101 | }
102 |
103 | selectionSort() {
104 | const animations = getSelectionSortAnimations(this.state.array);
105 | this.runSort(animations)
106 | }
107 |
108 | bubbleSort() {
109 | const animations = getBubbleSortAnimations(this.state.array);
110 | this.runSort(animations)
111 | }
112 |
113 | // addTime(start, end) {
114 | // const arrayContainer = document.querySelector(".array-container");
115 | // const bars = arrayContainer.firstChild;
116 | // const timeTaken = Math.round((end - start) / 1000)
117 | // const timeBox = document.createElement('div')
118 | // timeBox.classList.add("timeBox")
119 | // timeBox.innerHTML = `Elapsed time: ${timeTaken} seconds!`
120 | // arrayContainer.insertBefore(timeBox, bars)
121 | // }
122 |
123 | render() {
124 | const {array} = this.state;
125 |
126 | return (
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | {array.map((value, i) => (
138 |
145 | ))}
146 |
147 |
148 |
149 | );
150 | };
151 | }
--------------------------------------------------------------------------------
/src/sortingvisualizer/algorithms.js:
--------------------------------------------------------------------------------
1 |
2 | // this contains my implementations of the algotithms with just numbers for practice
3 |
4 | const Mergesort = (array) => {
5 |
6 | function mergeArrays(arr1, arr2) {
7 |
8 | const sortedArr = []
9 |
10 | while (arr1.length && arr2.length) {
11 | if (arr1[0] > arr2[0]) {
12 | sortedArr.push(arr2.shift())
13 | }
14 | else {
15 | sortedArr.push(arr1.shift())
16 | }
17 | }
18 |
19 | while (arr1.length) {
20 | sortedArr.push(arr1.shift())
21 | }
22 | while (arr2.length) {
23 | sortedArr.push(arr2.shift())
24 | }
25 |
26 | return sortedArr
27 |
28 | }
29 |
30 | if (array.length < 2) {
31 | return array
32 | }
33 | const middle = Math.floor(array.length/2)
34 |
35 | const subarrayA = array.slice(0, middle)
36 | const subarrayB = array.slice(middle, array.length)
37 |
38 | const sortedSubarrayA = Mergesort(subarrayA)
39 | const sortedSubarrayB = Mergesort(subarrayB)
40 |
41 | return mergeArrays(sortedSubarrayA, sortedSubarrayB)
42 |
43 | }
44 |
45 | const Quicksort = (array) => {
46 | // a function for swapping 2 elements
47 | function swap(arr, i1, i2) {
48 | const temp = arr[i2]
49 | arr[i2] = arr[i1]
50 | arr[i1] = temp
51 | }
52 | // base case
53 | if (array.length < 2) {
54 | return array
55 | }
56 | // select pivot element randomly
57 | const pivotIndex = 0
58 | // swap pivot to index 0. pivot element is thus forth at array[0]
59 | swap(array, pivotIndex, 0)
60 | // i denotes boundary between smaller and larger elements than pivot
61 | let i = 1
62 | // iterate over all elements larger than 0 (pivot). If the element is larger than
63 | // pivot, swap it with ith element and advance i
64 | for (let j = 1; j < array.length; j++) {
65 | //console.log(`iteration: ${j}`)
66 | if (array[j] < array[0]) {
67 | //console.log(array[j])
68 | swap(array, i, j)
69 | i++
70 | }
71 | }
72 | swap(array, 0, i-1)
73 | const leftArray = array.slice(0, i-1)
74 | const rightArray = array.slice(i, array.length)
75 | let completeArray = []
76 | // recursively apply the same to left and right subarrays and merge
77 | completeArray = completeArray.concat(...Quicksort(leftArray))
78 | completeArray.push(array[i-1])
79 | completeArray = completeArray.concat(...Quicksort(rightArray))
80 | return completeArray
81 | }
82 |
83 | const Bubblesort = (array) => {
84 | const length = array.length;
85 | for (let i = 0; i < length; i++) {
86 | for (let j = 0; j < length-i; j++) {
87 | if(array[j] > array[j+1]) {
88 | //Swap the numbers
89 | let temp = array[j]
90 | array[j] = array[j+1];
91 | array[j+1] = temp;
92 | }
93 | }
94 | }
95 |
96 | return array;
97 | }
98 |
99 | const Selectionsort = (array) => {
100 | const length = array.length;
101 | for(let i = 0; i < length; i++){
102 | // set current index as minimum
103 | let min = i;
104 | let temp = array[i];
105 | for(let j = i+1; j < length; j++){
106 | if (array[j] < array[min]){
107 | //update minimum if current is lower that what we had previously
108 | min = j;
109 | }
110 | }
111 | array[i] = array[min];
112 | array[min] = temp;
113 | }
114 | return array;
115 | }
116 |
117 |
118 | let functions = {Mergesort, Quicksort, Bubblesort, Selectionsort}
119 |
120 | export default functions
--------------------------------------------------------------------------------
/src/sortingvisualizer/sortingAlgorithms.js:
--------------------------------------------------------------------------------
1 | export function getBubbleSortAnimations(array) {
2 | const animations = []
3 | for (let i = 0; i < array.length; i++) {
4 | for (let j = 0; j < array.length-1-i; j++) {
5 | // change colours
6 | animations.push([j,j+1])
7 | // revert colours
8 | animations.push([j,j+1])
9 | if(array[j] > array[j+1]) {
10 | //Swap the numbers
11 | let temp = array[j]
12 | animations.push([j,array[j+1],j+1,array[j]])
13 | array[j] = array[j+1];
14 | array[j+1] = temp;
15 | }
16 | else {
17 | animations.push("noSwap")
18 | }
19 | }
20 | }
21 | return animations
22 | }
23 |
24 | export function getSelectionSortAnimations(array) {
25 | const animations = []
26 | for(let i = 0; i < array.length; i++){
27 | // set current index as minimum
28 | let min = i;
29 | let temp = array[i];
30 | for(let j = i+1; j < array.length; j++){
31 | // change colours
32 | animations.push([min,j])
33 | // revert colours
34 | animations.push([min,j])
35 | //no swap yet
36 | animations.push("noSwap")
37 | if (array[j] < array[min]){
38 | //update minimum if current is lower that what we had previously
39 | min = j;
40 | }
41 | }
42 | // change colours
43 | animations.push([i,min])
44 | // revert colours
45 | animations.push([i,min])
46 | // swap the numbers
47 | animations.push([i,array[min],min,array[i]])
48 | array[i] = array[min];
49 | array[min] = temp;
50 | }
51 | return animations;
52 | }
53 |
54 | export function getQuickSortAnimations(array) {
55 | const animations = []
56 | // a function for swapping 2 elements
57 | function swap(arr, i1, i2) {
58 | const temp = arr[i2]
59 | // change colours
60 | animations.push([i1,i2])
61 | // revert colours
62 | animations.push([i1,i2])
63 | // swap the numbers
64 | animations.push([i1,arr[i2],i2,arr[i1]])
65 | arr[i2] = arr[i1]
66 | arr[i1] = temp
67 | }
68 | // base case
69 | if (array.length < 2) {
70 | return array
71 | }
72 | // select pivot element randomly
73 | const pivotIndex = 0
74 | // swap pivot to index 0. pivot element is thus forth at array[0]
75 | swap(array, pivotIndex, 0)
76 | // i denotes boundary between smaller and larger elements than pivot
77 | let i = 1
78 | // iterate over all elements larger than 0 (pivot). If the element is larger than
79 | // pivot, swap it with ith element and advance i
80 | for (let j = 1; j < array.length; j++) {
81 | //console.log(`iteration: ${j}`)
82 | if (array[j] < array[0]) {
83 | //console.log(array[j])
84 | swap(array, i, j)
85 | i++
86 | }
87 | else {
88 | // change colours
89 | animations.push([i,j])
90 | // revert colours
91 | animations.push([i,j])
92 | //no swap yet
93 | animations.push("noSwap")
94 | }
95 | }
96 | swap(array, 0, i-1)
97 | const leftArray = array.slice(0, i-1)
98 | const rightArray = array.slice(i, array.length)
99 | // recursively apply the same to left and right subarrays and merge
100 | const auxiliaryArray = array.slice()
101 | QuickSortHelper(leftArray, animations, auxiliaryArray, 0)
102 | QuickSortHelper(rightArray, animations, auxiliaryArray, i)
103 | return animations
104 | }
105 |
106 | function QuickSortHelper(mainArray, animations, auxArray, startIdx) {
107 | // base case
108 | if (mainArray.length < 2) {
109 | return
110 | }
111 | function swapNpush(arr, i1, i2, auxArr, Idx1, Idx2) {
112 | // change colours
113 | animations.push([Idx1,Idx2])
114 | // revert colours
115 | animations.push([Idx1,Idx2])
116 | // swap the numbers
117 | animations.push([Idx1,auxArr[Idx2],Idx2,auxArr[Idx1]])
118 | swap(arr, i1, i2)
119 | swap(auxArr, Idx1, Idx2)
120 | }
121 | function swap(arr, i1, i2) {
122 | const temp = arr[i2]
123 | arr[i2] = arr[i1]
124 | arr[i1] = temp
125 | }
126 | const pivotIndex = 0
127 | swapNpush(mainArray, pivotIndex, 0, auxArray, startIdx, startIdx)
128 | let i = 1
129 | let iTracker = startIdx + 1
130 | let jTracker = startIdx + 1
131 | for (let j = 1; j < mainArray.length; j++) {
132 | if (mainArray[j] < mainArray[0]) {
133 | //console.log(array[j])
134 | swapNpush(mainArray, i, j, auxArray, iTracker, jTracker)
135 | i++
136 | iTracker++
137 | jTracker++
138 | }
139 | else {
140 | // change colours
141 | animations.push([iTracker,jTracker])
142 | // revert colours
143 | animations.push([iTracker,jTracker])
144 | //no swap yet
145 | animations.push("noSwap")
146 | jTracker++
147 | }
148 | }
149 | swapNpush(mainArray, 0, i-1, auxArray, startIdx, iTracker-1)
150 | const leftArray = mainArray.slice(0, i-1)
151 | const rightArray = mainArray.slice(i, mainArray.length)
152 | // recursively apply the same to left and right subarrays and merge
153 | QuickSortHelper(leftArray, animations, auxArray, startIdx)
154 | QuickSortHelper(rightArray, animations, auxArray, iTracker)
155 |
156 | }
157 | export function getMergeSortAnimations(array) {
158 | const animations = [];
159 | if (array.length <= 1) return array;
160 | const auxiliaryArray = array.slice();
161 | mergeSortHelper(array, 0, array.length - 1, auxiliaryArray, animations);
162 | return animations;
163 | }
164 |
165 | function mergeSortHelper(
166 | mainArray,
167 | startIdx,
168 | endIdx,
169 | auxiliaryArray,
170 | animations,
171 | ) {
172 | if (startIdx === endIdx) return;
173 | const middleIdx = Math.floor((startIdx + endIdx) / 2);
174 | mergeSortHelper(auxiliaryArray, startIdx, middleIdx, mainArray, animations);
175 | mergeSortHelper(auxiliaryArray, middleIdx + 1, endIdx, mainArray, animations);
176 | doMerge(mainArray, startIdx, middleIdx, endIdx, auxiliaryArray, animations);
177 | }
178 |
179 | function doMerge(
180 | mainArray,
181 | startIdx,
182 | middleIdx,
183 | endIdx,
184 | auxiliaryArray,
185 | animations,
186 | ) {
187 | let k = startIdx;
188 | let i = startIdx;
189 | let j = middleIdx + 1;
190 | while (i <= middleIdx && j <= endIdx) {
191 | // These are the values that we're comparing; we push them once
192 | // to change their color.
193 | animations.push([i, j]);
194 | // These are the values that we're comparing; we push them a second
195 | // time to revert their color.
196 | animations.push([i, j]);
197 | if (auxiliaryArray[i] <= auxiliaryArray[j]) {
198 | // We overwrite the value at index k in the original array with the
199 | // value at index i in the auxiliary array.
200 | animations.push([k, auxiliaryArray[i]]);
201 | mainArray[k++] = auxiliaryArray[i++];
202 | } else {
203 | // We overwrite the value at index k in the original array with the
204 | // value at index j in the auxiliary array.
205 | animations.push([k, auxiliaryArray[j]]);
206 | mainArray[k++] = auxiliaryArray[j++];
207 | }
208 | }
209 | while (i <= middleIdx) {
210 | // These are the values that we're comparing; we push them once
211 | // to change their color.
212 | animations.push([i, i]);
213 | // These are the values that we're comparing; we push them a second
214 | // time to revert their color.
215 | animations.push([i, i]);
216 | // We overwrite the value at index k in the original array with the
217 | // value at index i in the auxiliary array.
218 | animations.push([k, auxiliaryArray[i]]);
219 | mainArray[k++] = auxiliaryArray[i++];
220 | }
221 | while (j <= endIdx) {
222 | // These are the values that we're comparing; we push them once
223 | // to change their color.
224 | animations.push([j, j]);
225 | // These are the values that we're comparing; we push them a second
226 | // time to revert their color.
227 | animations.push([j, j]);
228 | // We overwrite the value at index k in the original array with the
229 | // value at index j in the auxiliary array.
230 | animations.push([k, auxiliaryArray[j]]);
231 | mainArray[k++] = auxiliaryArray[j++];
232 | }
233 | }
--------------------------------------------------------------------------------