Skip to content

Commit b60f3a2

Browse files
feat: add Quick Sort Visualizer (web + CLI)
- Add Quick Sort web visualizer with pivot-based partitioning - Add Ascending/Descending toggle for both web and CLI versions - Highlight pivot, comparing, swapping, and sorted states with colors - Add Quick-Sort.py CLI version following Bubble-Sort.py pattern - Add test_quick_sort.py with 9 passing tests - Register quick-sort in getProjectHTML and initializers maps - Add project card and script tag in index.html - Generate banner image for Quick Sort - Update README.md with Quick Sort entry and project count
1 parent 4fd9742 commit b60f3a2

8 files changed

Lines changed: 607 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ deactivate
240240

241241
## 🌐 Web App Catalog
242242

243-
The browser app currently exposes 39 projects:
243+
The browser app currently exposes 40 projects:
244244

245245
### Games
246246

@@ -271,6 +271,7 @@ The browser app currently exposes 39 projects:
271271
- Armstrong Numbers
272272
- Binary Search
273273
- Bubble Sort
274+
- Quick Sort
274275
- Calculator
275276
- Collatz Conjecture
276277
- Coordinate to Polar

math/Quick-Sort/Quick-Sort.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import sys
2+
import os
3+
4+
# Add project root to sys.path
5+
if "__file__" in globals():
6+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
7+
else:
8+
sys.path.append(os.path.abspath(os.getcwd()))
9+
10+
from utils.validation import get_choice, get_int_list
11+
12+
13+
def quick_sort(arr: list[int], reverse: bool = False) -> list[int]:
14+
"""Sorts a list of integers using the Quick Sort algorithm.
15+
16+
Args:
17+
arr: The list of integers to sort.
18+
reverse: If True, sorts in descending order. Otherwise, ascending.
19+
20+
Returns:
21+
A new sorted list.
22+
"""
23+
# Base case: a list of 0 or 1 elements is already sorted
24+
if len(arr) <= 1:
25+
return arr.copy()
26+
27+
# Choose the last element as the pivot
28+
pivot = arr[-1]
29+
left = []
30+
right = []
31+
32+
# Partition the remaining elements around the pivot
33+
for value in arr[:-1]:
34+
if not reverse and value <= pivot:
35+
left.append(value)
36+
elif not reverse and value > pivot:
37+
right.append(value)
38+
elif reverse and value >= pivot:
39+
left.append(value)
40+
else:
41+
right.append(value)
42+
43+
# Recursively sort left and right partitions, then combine
44+
return quick_sort(left, reverse) + [pivot] + quick_sort(right, reverse)
45+
46+
47+
def main() -> None:
48+
print("=" * 50)
49+
print("⚡ QUICK SORT INTERACTIVE TOOL ⚡")
50+
print("=" * 50)
51+
print("Sort a list of numbers in Ascending or Descending order.\n")
52+
53+
while True:
54+
print("=" * 50)
55+
56+
arr = get_int_list(
57+
prompt="➡️ Enter numbers to sort separated by spaces (e.g., 64 34 25): ",
58+
error_empty="❌ Error: Input cannot be empty!",
59+
error_invalid="❌ Error: Please enter valid integers only."
60+
)
61+
62+
print("\nChoose sorting order:")
63+
print("1️⃣ Ascending")
64+
print("2️⃣ Descending")
65+
66+
order_choice = get_choice(
67+
prompt="🎯 Enter your choice (1 or 2): ",
68+
choices=["1", "2"],
69+
error_invalid="❌ Invalid sorting choice! Please select 1 or 2."
70+
)
71+
72+
reverse = (order_choice == "2")
73+
74+
sorted_arr = quick_sort(arr, reverse)
75+
76+
print(f"\n📊 Original list: {arr}")
77+
if reverse:
78+
print(f"✅ Sorted list (Descending): {sorted_arr}")
79+
else:
80+
print(f"✅ Sorted list (Ascending): {sorted_arr}")
81+
82+
again = input("\n🔄 Do you want to sort another list? (y/n): ").strip().lower()
83+
if again != 'y':
84+
print("\n👋 Thanks for using Quick Sort Tool! Goodbye!\n")
85+
break
86+
87+
88+
if __name__ == "__main__":
89+
main()

tests/test_quick_sort.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import unittest
2+
from unittest.mock import patch
3+
import io
4+
import os
5+
import importlib.util
6+
7+
# Absolute path to Quick-Sort.py
8+
file_path = os.path.join(
9+
os.path.dirname(__file__), "..",
10+
"math", "Quick-Sort", "Quick-Sort.py"
11+
)
12+
file_path = os.path.abspath(file_path)
13+
14+
# Load module dynamically
15+
spec = importlib.util.spec_from_file_location("quick_sort_module", file_path)
16+
quick_sort_module = importlib.util.module_from_spec(spec)
17+
spec.loader.exec_module(quick_sort_module)
18+
19+
quick_sort = quick_sort_module.quick_sort
20+
main = quick_sort_module.main
21+
22+
23+
class TestQuickSort(unittest.TestCase):
24+
25+
def test_quick_sort_ascending(self):
26+
# Ascending order
27+
self.assertEqual(quick_sort([64, 34, 25, 12, 22, 11, 90]), [11, 12, 22, 25, 34, 64, 90])
28+
self.assertEqual(quick_sort([5, 1, 4, 2, 8]), [1, 2, 4, 5, 8])
29+
30+
def test_quick_sort_descending(self):
31+
# Descending order
32+
self.assertEqual(quick_sort([64, 34, 25, 12, 22, 11, 90], reverse=True), [90, 64, 34, 25, 22, 12, 11])
33+
self.assertEqual(quick_sort([5, 1, 4, 2, 8], reverse=True), [8, 5, 4, 2, 1])
34+
35+
def test_quick_sort_empty_and_single_item(self):
36+
# Empty array
37+
self.assertEqual(quick_sort([]), [])
38+
# Single-item array
39+
self.assertEqual(quick_sort([42]), [42])
40+
41+
def test_quick_sort_negative_integers(self):
42+
# Lists with negative integers
43+
self.assertEqual(quick_sort([-5, -1, -10, 0, 5]), [-10, -5, -1, 0, 5])
44+
self.assertEqual(quick_sort([-5, -1, -10, 0, 5], reverse=True), [5, 0, -1, -5, -10])
45+
46+
def test_quick_sort_already_sorted(self):
47+
# Already sorted ascending
48+
self.assertEqual(quick_sort([1, 2, 3, 4, 5]), [1, 2, 3, 4, 5])
49+
# Already sorted descending
50+
self.assertEqual(quick_sort([5, 4, 3, 2, 1], reverse=True), [5, 4, 3, 2, 1])
51+
52+
def test_quick_sort_with_duplicates(self):
53+
# Lists with duplicate values
54+
self.assertEqual(quick_sort([5, 3, 8, 3, 9, 1, 5]), [1, 3, 3, 5, 5, 8, 9])
55+
56+
@patch('builtins.input')
57+
@patch('sys.stdout', new_callable=io.StringIO)
58+
def test_main_flow_ascending(self, mock_stdout, mock_input):
59+
# Ascending sort flow
60+
mock_input.side_effect = ["64 34 25", "1", "n"]
61+
main()
62+
output = mock_stdout.getvalue()
63+
self.assertIn("Original list: [64, 34, 25]", output)
64+
self.assertIn("Sorted list (Ascending): [25, 34, 64]", output)
65+
66+
@patch('builtins.input')
67+
@patch('sys.stdout', new_callable=io.StringIO)
68+
def test_main_flow_descending(self, mock_stdout, mock_input):
69+
# Descending sort flow
70+
mock_input.side_effect = ["64 34 25", "2", "n"]
71+
main()
72+
output = mock_stdout.getvalue()
73+
self.assertIn("Original list: [64, 34, 25]", output)
74+
self.assertIn("Sorted list (Descending): [64, 34, 25]", output)
75+
76+
@patch('builtins.input')
77+
@patch('sys.stdout', new_callable=io.StringIO)
78+
def test_main_flow_invalid_inputs(self, mock_stdout, mock_input):
79+
# Invalid inputs validation flow
80+
mock_input.side_effect = ["", "64 abc 25", "64 34 25", "3", "64 34 25", "1", "y", "12 11", "2", "n"]
81+
main()
82+
output = mock_stdout.getvalue()
83+
self.assertIn("Error: Input cannot be empty!", output)
84+
self.assertIn("Error: Please enter valid integers only.", output)
85+
self.assertIn("Invalid sorting choice! Please select 1 or 2.", output)
86+
self.assertIn("Sorted list (Ascending): [25, 34, 64]", output)
87+
self.assertIn("Sorted list (Descending): [12, 11]", output)
88+
89+
90+
if __name__ == '__main__':
91+
unittest.main()
16.3 KB
Loading

web-app/generate_banners.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ def draw_o(ox, oy):
616616
("Projectile Motion", "math", "projectile-motion.webp"),
617617
("Binary Search", "math", "binary-search.webp"),
618618
("Bubble Sort", "math", "bubble-sort.webp"),
619+
("Quick Sort", "math", "quick-sort.webp"),
619620
("Tower of Hanoi", "math", "tower-of-hanoi.webp"),
620621
("Matrix Calculator", "math", "matrix-calculator.webp"),
621622
("Fourier Series", "math", "fourier-series.webp"),

web-app/index.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ <h3>Legal</h3>
839839
<script defer src="js/projects/number-guessing.js"></script>
840840
<script defer src="js/projects/binary-search.js"></script>
841841
<script defer src="js/projects/bubble-sort.js"></script>
842+
<script defer src="js/projects/quick-sort.js"></script>
842843
<script defer src="js/projects/hangman.js"></script>
843844
<script defer src="js/projects/flames.js"></script>
844845
<script defer src="js/projects/fibonacci.js"></script>
@@ -1038,6 +1039,13 @@ <h3>Legal</h3>
10381039
desc: "Visualize bubble sort algorithm",
10391040
tags: "math,algorithm",
10401041
},
1042+
{
1043+
project: "quick-sort",
1044+
title: "Quick Sort",
1045+
category: "math",
1046+
desc: "Visualize quick sort algorithm with pivot partitioning",
1047+
tags: "math,algorithm",
1048+
},
10411049
{
10421050
project: "fibonacci",
10431051
title: "Fibonacci",

web-app/js/projects.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function getProjectHTML(projectName) {
2828
'reverse-hangman': () => getReverseHangmanHTML,
2929
'snake-game': getSnakeGameHTML(),
3030
'bubble-sort': getBubbleSortHTML(),
31+
'quick-sort': getQuickSortHTML(),
3132
'fourier-series': getFourierSeriesHTML()
3233
};
3334

@@ -1836,6 +1837,15 @@ const projectInstructions = {
18361837
"Watch the sorting visualization"
18371838
]
18381839
},
1840+
"quick-sort": {
1841+
title: "⚡ How Quick Sort Works",
1842+
steps: [
1843+
"Enter an array of numbers",
1844+
"A pivot element is chosen",
1845+
"Smaller elements go left, larger go right",
1846+
"Process repeats until fully sorted"
1847+
]
1848+
},
18391849
"tower-of-hanoi": {
18401850
title: "🗼 How to Solve Tower of Hanoi",
18411851
steps: [
@@ -3167,6 +3177,7 @@ function initializeProject(projectName) {
31673177
fibonacci: "initFibonacci",
31683178
"binary-search": "initBinarySearch",
31693179
"bubble-sort": "initBubbleSort",
3180+
"quick-sort": "initQuickSort",
31703181
"progression-recognizer": "initProgressionRecognizer",
31713182
"pascal-triangle": "initPascalTriangle",
31723183
armstrong: "initArmstrong",

0 commit comments

Comments
 (0)