Skip to content

Commit 5431b1d

Browse files
committed
Add new sorting algorithms
1 parent 44624b6 commit 5431b1d

8 files changed

Lines changed: 304 additions & 2 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ https://github.com/user-attachments/assets/2b9674c5-c705-4a22-ba6a-474cfc11cddd
3939
- [Exchange Sort](https://en.wikipedia.org/wiki/Sorting_algorithm#Exchange_sort)
4040
- [Odd-Even Sort](https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort)
4141

42+
### 🆕 Recently Added Algorithms
43+
44+
- [Counting Sort](https://en.wikipedia.org/wiki/Counting_sort) - Non-comparison sorting algorithm that counts occurrences of each value
45+
- [Comb Sort](https://en.wikipedia.org/wiki/Comb_sort) - Improved Bubble Sort variant that uses gap-based comparisons
46+
- [Intro Sort](https://en.wikipedia.org/wiki/Introsort) - Hybrid algorithm combining Quick Sort, Heap Sort, and Insertion Sort
47+
4248
## 🔍 Rationale
4349

4450
Visual Sorting was created to explore and learn Svelte, a modern JavaScript framework. Inspired by Timo Bingmann's captivating video "The Sound of Sorting" (https://www.youtube.com/watch?v=kPRA0W1kECg), which combines sorting algorithm visualizations with sound, I wanted to develop a tool that offers a similar educational and engaging experience. Visual Sorting aims to make learning sorting algorithms both enjoyable and insightful through dynamic visuals and sounds.

src/lib/components/AlgorithmSelector.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,17 @@
2121
<li>
2222
<button
2323
class={algo.name === selectedAlgorithm?.name ? 'active' : ''}
24-
on:click={click(algo)}>{algo.name}</button
24+
on:click={click(algo)}
2525
>
26+
<span class="flex items-center gap-2">
27+
{algo.name}
28+
{#if algo.badge}
29+
<span class="badge badge-primary badge-sm"
30+
>{algo.badge}</span
31+
>
32+
{/if}
33+
</span>
34+
</button>
2635
</li>
2736
{#if index === 2 && group > 0}
2837
<ArrowLeft {group} />

src/lib/components/mobile/MobileAlgorithmSelector.svelte

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,11 @@
2020
{#each algos as algo, index}
2121
<option
2222
value={[group, index]}
23-
selected={algo.name === selectedAlgorithm?.name}>{algo.name}</option
23+
selected={algo.name === selectedAlgorithm?.name}
2424
>
25+
{algo.name}{#if algo.badge}
26+
({algo.badge}){/if}
27+
</option>
2528
{/each}
2629
{/each}
2730
</select>

src/lib/sort-algorithms/algorithms.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ import { bitonicSort } from './bitonic-sort';
33
import { bogoSort } from './bogo-sort';
44
import { bubleSort } from './buble-sort';
55
import { cocktailSort } from './cocktail-sort';
6+
import { combSort } from './comb-sort';
7+
import { countingSort } from './counting-sort';
68
import { cycleSort } from './cycle-sort';
79
import { exchangeSort } from './exchange-sort';
810
import { gnomeSort } from './gnome-sort';
911
import { heapSort } from './heap-sort';
1012
import { insertionSort } from './insertion-sort';
13+
import { introSort } from './intro-sort';
1114
import { mergeSort } from './merge-sort';
1215
import { oddEvenSort } from './odd-even-sort';
1316
import { pancakeSort } from './pancake-sort';
@@ -101,5 +104,20 @@ export const algorithms: AlgorithmDefinition[][] = [
101104
name: 'Odd Even Sort',
102105
function: oddEvenSort,
103106
},
107+
{
108+
name: 'Counting Sort',
109+
function: countingSort,
110+
badge: 'new',
111+
},
112+
{
113+
name: 'Comb Sort',
114+
function: combSort,
115+
badge: 'new',
116+
},
117+
{
118+
name: 'Intro Sort',
119+
function: introSort,
120+
badge: 'new',
121+
},
104122
],
105123
];
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { SortingGenerator } from './types';
2+
3+
/**
4+
* Comb Sort Algorithm
5+
*
6+
* An improved version of Bubble Sort that uses a gap-based approach.
7+
* It starts with a large gap and reduces the gap by a shrink factor
8+
* (typically 1.3) until the gap becomes 1, at which point it becomes
9+
* a regular Bubble Sort.
10+
*
11+
* Time Complexity: O(n²) worst case, O(n log n) average case
12+
* Space Complexity: O(1)
13+
*
14+
* @param arr - Array of numbers to sort
15+
* @yields ProgressIndicator - Visualization data for each step
16+
*/
17+
export const combSort = function* (arr: number[]): SortingGenerator {
18+
const n = arr.length;
19+
if (n <= 1) return;
20+
21+
// Initialize gap
22+
let gap = n;
23+
const shrinkFactor = 1.3;
24+
let swapped = true;
25+
26+
// Continue until gap is 1 and no swaps occurred
27+
while (gap > 1 || swapped) {
28+
// Update gap using shrink factor
29+
gap = Math.floor(gap / shrinkFactor);
30+
if (gap < 1) gap = 1;
31+
32+
swapped = false;
33+
34+
// Compare elements with current gap
35+
for (let i = 0; i < n - gap; i++) {
36+
yield { access: [i, i + gap], sound: i + gap };
37+
38+
if (arr[i] > arr[i + gap]) {
39+
// Swap elements
40+
const temp = arr[i];
41+
arr[i] = arr[i + gap];
42+
arr[i + gap] = temp;
43+
swapped = true;
44+
}
45+
}
46+
}
47+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { SortingGenerator } from './types';
2+
3+
/**
4+
* Counting Sort Algorithm
5+
*
6+
* A non-comparison sorting algorithm that works by counting the number of objects
7+
* having distinct key values. It operates by counting the number of objects that
8+
* have each distinct key value, and using arithmetic on those counts to determine
9+
* the positions of each key value in the output sequence.
10+
*
11+
* Time Complexity: O(n + k) where n is the number of elements and k is the range of input
12+
* Space Complexity: O(k)
13+
*
14+
* @param arr - Array of numbers to sort
15+
* @yields ProgressIndicator - Visualization data for each step
16+
*/
17+
export const countingSort = function* (arr: number[]): SortingGenerator {
18+
if (arr.length === 0) return;
19+
20+
// Find the maximum value in the array
21+
let max = arr[0];
22+
for (let i = 1; i < arr.length; i++) {
23+
yield { access: [i], sound: i };
24+
if (arr[i] > max) {
25+
max = arr[i];
26+
}
27+
}
28+
29+
// Create count array with size max + 1
30+
const count = new Array(max + 1).fill(0);
31+
32+
// Count the frequency of each element
33+
for (let i = 0; i < arr.length; i++) {
34+
yield { access: [i], sound: i };
35+
count[arr[i]]++;
36+
}
37+
38+
// Modify count array to store cumulative count
39+
for (let i = 1; i <= max; i++) {
40+
yield { access: [i - 1], sound: i - 1 };
41+
count[i] += count[i - 1];
42+
}
43+
44+
// Create output array
45+
const output = new Array(arr.length);
46+
47+
// Build the output array
48+
for (let i = arr.length - 1; i >= 0; i--) {
49+
yield { access: [i], sound: i };
50+
output[count[arr[i]] - 1] = arr[i];
51+
count[arr[i]]--;
52+
}
53+
54+
// Copy the output array back to the original array
55+
for (let i = 0; i < arr.length; i++) {
56+
yield { access: [i], sound: i };
57+
arr[i] = output[i];
58+
}
59+
};
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import type { SortingGenerator } from './types';
2+
3+
/**
4+
* Intro Sort (Introsort) Algorithm
5+
*
6+
* A hybrid sorting algorithm that combines Quick Sort, Heap Sort, and Insertion Sort.
7+
* It starts with Quick Sort, switches to Heap Sort when the recursion depth becomes
8+
* too deep, and uses Insertion Sort for small subarrays.
9+
*
10+
* Time Complexity: O(n log n) guaranteed worst case
11+
* Space Complexity: O(log n)
12+
*
13+
* @param arr - Array of numbers to sort
14+
* @yields ProgressIndicator - Visualization data for each step
15+
*/
16+
export const introSort = function* (arr: number[]): SortingGenerator {
17+
const n = arr.length;
18+
if (n <= 1) return;
19+
20+
// Calculate maximum recursion depth (2 * log2(n))
21+
const maxDepth = Math.floor(2 * Math.log2(n));
22+
23+
yield* introSortRecursive(arr, 0, n - 1, maxDepth);
24+
};
25+
26+
function* introSortRecursive(
27+
arr: number[],
28+
low: number,
29+
high: number,
30+
depthLimit: number
31+
): SortingGenerator {
32+
const size = high - low + 1;
33+
34+
// Use insertion sort for small arrays
35+
if (size < 16) {
36+
yield* insertionSort(arr, low, high);
37+
return;
38+
}
39+
40+
// If depth limit reached, switch to heap sort
41+
if (depthLimit === 0) {
42+
yield* heapSortRange(arr, low, high);
43+
return;
44+
}
45+
46+
// Use quick sort for larger arrays
47+
const pivotIndex = yield* partition(arr, low, high);
48+
49+
// Recursively sort the two partitions
50+
yield* introSortRecursive(arr, low, pivotIndex - 1, depthLimit - 1);
51+
yield* introSortRecursive(arr, pivotIndex + 1, high, depthLimit - 1);
52+
}
53+
54+
function* partition(
55+
arr: number[],
56+
low: number,
57+
high: number
58+
): Generator<ProgressIndicator, number, unknown> {
59+
// Choose middle element as pivot
60+
const pivotIndex = Math.floor((low + high) / 2);
61+
const pivot = arr[pivotIndex];
62+
63+
// Move pivot to end
64+
[arr[pivotIndex], arr[high]] = [arr[high], arr[pivotIndex]];
65+
yield { access: [pivotIndex, high], sound: high };
66+
67+
let i = low - 1;
68+
69+
for (let j = low; j < high; j++) {
70+
yield { access: [j], sound: j };
71+
72+
if (arr[j] <= pivot) {
73+
i++;
74+
if (i !== j) {
75+
[arr[i], arr[j]] = [arr[j], arr[i]];
76+
yield { access: [i, j], sound: j };
77+
}
78+
}
79+
}
80+
81+
// Move pivot to its correct position
82+
[arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
83+
yield { access: [i + 1, high], sound: high };
84+
85+
return i + 1;
86+
}
87+
88+
function* insertionSort(
89+
arr: number[],
90+
low: number,
91+
high: number
92+
): SortingGenerator {
93+
for (let i = low + 1; i <= high; i++) {
94+
const key = arr[i];
95+
let j = i - 1;
96+
97+
yield { access: [i], sound: i };
98+
99+
while (j >= low && arr[j] > key) {
100+
yield { access: [j, j + 1], sound: j + 1 };
101+
arr[j + 1] = arr[j];
102+
j--;
103+
}
104+
105+
if (j + 1 !== i) {
106+
arr[j + 1] = key;
107+
yield { access: [j + 1], sound: j + 1 };
108+
}
109+
}
110+
}
111+
112+
function* heapSortRange(
113+
arr: number[],
114+
low: number,
115+
high: number
116+
): SortingGenerator {
117+
const n = high - low + 1;
118+
119+
// Build max heap
120+
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
121+
yield* heapify(arr, low, n, i);
122+
}
123+
124+
// Extract elements from heap one by one
125+
for (let i = n - 1; i > 0; i--) {
126+
// Move current root to end
127+
[arr[low], arr[low + i]] = [arr[low + i], arr[low]];
128+
yield { access: [low, low + i], sound: low + i };
129+
130+
// Call heapify on the reduced heap
131+
yield* heapify(arr, low, i, 0);
132+
}
133+
}
134+
135+
function* heapify(
136+
arr: number[],
137+
low: number,
138+
n: number,
139+
i: number
140+
): SortingGenerator {
141+
let largest = i;
142+
const left = 2 * i + 1;
143+
const right = 2 * i + 2;
144+
145+
if (left < n && arr[low + left] > arr[low + largest]) {
146+
largest = left;
147+
}
148+
149+
if (right < n && arr[low + right] > arr[low + largest]) {
150+
largest = right;
151+
}
152+
153+
if (largest !== i) {
154+
[arr[low + i], arr[low + largest]] = [arr[low + largest], arr[low + i]];
155+
yield { access: [low + i, low + largest], sound: low + largest };
156+
157+
yield* heapify(arr, low, n, largest);
158+
}
159+
}

src/lib/sort-algorithms/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ export type AlgorithmDefinition = {
1313
name: string;
1414
function: (arr: number[]) => SortingGenerator;
1515
arraySizeComponent?: typeof SvelteComponent<{ size: number }>;
16+
badge?: string;
1617
};

0 commit comments

Comments
 (0)