Skip to content

Commit 54337fb

Browse files
Merge branch 'main' into theme-selector
2 parents ba36c50 + 177aaec commit 54337fb

14 files changed

Lines changed: 1866 additions & 46 deletions

File tree

math/Heap-Sort/Heap-Sort.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 _sift_down(arr: list[int], start: int, end: int, reverse: bool) -> None:
14+
"""Restores the heap property for the subtree rooted at `start`.
15+
16+
Args:
17+
arr: The list being heapified (mutated in place).
18+
start: Index of the subtree root to sift down from.
19+
end: Last valid index of the active heap region.
20+
reverse: If True, maintains a min-heap (for descending sort).
21+
Otherwise, maintains a max-heap (for ascending sort).
22+
"""
23+
root = start
24+
25+
while True:
26+
child = 2 * root + 1
27+
if child > end:
28+
break
29+
30+
# Pick the child that should be closer to the root
31+
if child + 1 <= end:
32+
if not reverse and arr[child] < arr[child + 1]:
33+
child += 1
34+
elif reverse and arr[child] > arr[child + 1]:
35+
child += 1
36+
37+
should_swap = (not reverse and arr[root] < arr[child]) or (reverse and arr[root] > arr[child])
38+
39+
if should_swap:
40+
arr[root], arr[child] = arr[child], arr[root]
41+
root = child
42+
else:
43+
break
44+
45+
46+
def heap_sort(arr: list[int], reverse: bool = False) -> list[int]:
47+
"""Sorts a list of integers using the Heap Sort algorithm.
48+
49+
Args:
50+
arr: The list of integers to sort.
51+
reverse: If True, sorts in descending order. Otherwise, ascending.
52+
53+
Returns:
54+
A new sorted list.
55+
"""
56+
result = arr.copy()
57+
n = len(result)
58+
59+
# Phase 1: Build a max-heap (ascending) or min-heap (descending)
60+
for start in range(n // 2 - 1, -1, -1):
61+
_sift_down(result, start, n - 1, reverse)
62+
63+
# Phase 2: Repeatedly extract the root and shrink the heap
64+
for end in range(n - 1, 0, -1):
65+
result[0], result[end] = result[end], result[0]
66+
_sift_down(result, 0, end - 1, reverse)
67+
68+
return result
69+
70+
71+
def main() -> None:
72+
print("=" * 50)
73+
print("🏔️ HEAP SORT INTERACTIVE TOOL 🏔️")
74+
print("=" * 50)
75+
print("Sort a list of numbers in Ascending or Descending order.\n")
76+
77+
while True:
78+
print("=" * 50)
79+
80+
arr = get_int_list(
81+
prompt="➡️ Enter numbers to sort separated by spaces (e.g., 64 34 25): ",
82+
error_empty="❌ Error: Input cannot be empty!",
83+
error_invalid="❌ Error: Please enter valid integers only."
84+
)
85+
86+
print("\nChoose sorting order:")
87+
print("1️⃣ Ascending")
88+
print("2️⃣ Descending")
89+
90+
order_choice = get_choice(
91+
prompt="🎯 Enter your choice (1 or 2): ",
92+
choices=["1", "2"],
93+
error_invalid="❌ Invalid sorting choice! Please select 1 or 2."
94+
)
95+
96+
reverse = (order_choice == "2")
97+
98+
sorted_arr = heap_sort(arr, reverse)
99+
100+
print(f"\n📊 Original list: {arr}")
101+
if reverse:
102+
print(f"✅ Sorted list (Descending): {sorted_arr}")
103+
else:
104+
print(f"✅ Sorted list (Ascending): {sorted_arr}")
105+
106+
again = input("\n🔄 Do you want to sort another list? (y/n): ").strip().lower()
107+
if again != 'y':
108+
print("\n👋 Thanks for using Heap Sort Tool! Goodbye!\n")
109+
break
110+
111+
112+
if __name__ == "__main__":
113+
main()

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ python = ">=3.10,<3.13"
99
pygame = "2.6.1"
1010
numpy = "1.26.4"
1111
matplotlib = "3.8.3"
12-
pillow = "12.2.0"
12+
pillow = "12.3.0"
1313
requests = "2.34.2"
1414
markdown2 = "2.5.5"
1515
reportlab = "4.5.1"
16-
nltk = "3.9.1"
16+
nltk = "3.10.0"
1717

1818

1919
[tool.poetry.group.dev.dependencies]

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ markdown2==2.5.5
1616
reportlab==4.5.1
1717

1818
# NLP dependencies
19-
nltk==3.9.1
19+
nltk==3.10.0
2020
pyenchant==3.3.0
2121

2222
# TUI dependencies

tests/test_heap_sort.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import unittest
2+
from unittest.mock import patch
3+
import io
4+
import os
5+
import importlib.util
6+
7+
# Absolute path to Heap-Sort.py
8+
file_path = os.path.join(
9+
os.path.dirname(__file__), "..",
10+
"math", "Heap-Sort", "Heap-Sort.py"
11+
)
12+
file_path = os.path.abspath(file_path)
13+
14+
# Load module dynamically
15+
spec = importlib.util.spec_from_file_location("heap_sort_module", file_path)
16+
heap_sort_module = importlib.util.module_from_spec(spec)
17+
spec.loader.exec_module(heap_sort_module)
18+
19+
heap_sort = heap_sort_module.heap_sort
20+
main = heap_sort_module.main
21+
22+
23+
class TestHeapSort(unittest.TestCase):
24+
25+
def test_heap_sort_ascending(self):
26+
self.assertEqual(heap_sort([64, 34, 25, 12, 22, 11, 90]), [11, 12, 22, 25, 34, 64, 90])
27+
self.assertEqual(heap_sort([5, 1, 4, 2, 8]), [1, 2, 4, 5, 8])
28+
29+
def test_heap_sort_descending(self):
30+
self.assertEqual(heap_sort([64, 34, 25, 12, 22, 11, 90], reverse=True), [90, 64, 34, 25, 22, 12, 11])
31+
self.assertEqual(heap_sort([5, 1, 4, 2, 8], reverse=True), [8, 5, 4, 2, 1])
32+
33+
def test_heap_sort_empty_and_single_item(self):
34+
self.assertEqual(heap_sort([]), [])
35+
self.assertEqual(heap_sort([42]), [42])
36+
37+
def test_heap_sort_negative_integers(self):
38+
self.assertEqual(heap_sort([-5, -1, -10, 0, 5]), [-10, -5, -1, 0, 5])
39+
self.assertEqual(heap_sort([-5, -1, -10, 0, 5], reverse=True), [5, 0, -1, -5, -10])
40+
41+
def test_heap_sort_already_sorted(self):
42+
self.assertEqual(heap_sort([1, 2, 3, 4, 5]), [1, 2, 3, 4, 5])
43+
self.assertEqual(heap_sort([5, 4, 3, 2, 1], reverse=True), [5, 4, 3, 2, 1])
44+
45+
def test_heap_sort_with_duplicates(self):
46+
self.assertEqual(heap_sort([5, 3, 8, 3, 9, 1, 5]), [1, 3, 3, 5, 5, 8, 9])
47+
48+
def test_heap_sort_does_not_mutate_original(self):
49+
original = [5, 3, 1, 4, 2]
50+
original_copy = original.copy()
51+
heap_sort(original)
52+
self.assertEqual(original, original_copy)
53+
54+
@patch('builtins.input')
55+
@patch('sys.stdout', new_callable=io.StringIO)
56+
def test_main_flow_ascending(self, mock_stdout, mock_input):
57+
mock_input.side_effect = ["64 34 25", "1", "n"]
58+
main()
59+
output = mock_stdout.getvalue()
60+
self.assertIn("Original list: [64, 34, 25]", output)
61+
self.assertIn("Sorted list (Ascending): [25, 34, 64]", output)
62+
63+
@patch('builtins.input')
64+
@patch('sys.stdout', new_callable=io.StringIO)
65+
def test_main_flow_invalid_inputs(self, mock_stdout, mock_input):
66+
mock_input.side_effect = ["", "64 abc 25", "64 34 25", "3", "64 34 25", "1", "y", "12 11", "2", "n"]
67+
main()
68+
output = mock_stdout.getvalue()
69+
self.assertIn("Error: Input cannot be empty!", output)
70+
self.assertIn("Error: Please enter valid integers only.", output)
71+
self.assertIn("Invalid sorting choice! Please select 1 or 2.", output)
72+
self.assertIn("Sorted list (Ascending): [25, 34, 64]", output)
73+
self.assertIn("Sorted list (Descending): [12, 11]", output)
74+
75+
76+
if __name__ == '__main__':
77+
unittest.main()

web-app/css/styles.css

Lines changed: 137 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,6 +1051,129 @@ main>.hero-section:has(.hero-code-snippets) {
10511051
padding: 0 1rem;
10521052
}
10531053

1054+
/* ==========================================================
1055+
FIX ISSUE #1703
1056+
Homepage Category Navigation
1057+
========================================================== */
1058+
1059+
.hero-category-nav{
1060+
1061+
display:flex;
1062+
1063+
justify-content:center;
1064+
1065+
align-items:center;
1066+
1067+
gap:18px;
1068+
1069+
width:fit-content;
1070+
1071+
margin:0 auto 42px;
1072+
1073+
padding:12px 24px;
1074+
1075+
background:rgba(255,255,255,.72);
1076+
1077+
backdrop-filter:blur(18px);
1078+
1079+
border:1px solid rgba(255,255,255,.35);
1080+
1081+
border-radius:18px;
1082+
1083+
box-shadow:0 8px 30px rgba(0,0,0,.08);
1084+
1085+
}
1086+
1087+
.hero-nav-btn{
1088+
1089+
display:flex;
1090+
1091+
align-items:center;
1092+
1093+
gap:8px;
1094+
1095+
background:none;
1096+
1097+
border:none;
1098+
1099+
cursor:pointer;
1100+
1101+
padding:10px 4px;
1102+
1103+
position:relative;
1104+
1105+
color:var(--text-secondary);
1106+
1107+
font-size:15px;
1108+
1109+
font-weight:600;
1110+
1111+
transition:.25s;
1112+
1113+
}
1114+
1115+
.hero-nav-btn i{
1116+
1117+
font-size:14px;
1118+
1119+
}
1120+
1121+
.hero-nav-btn:hover{
1122+
1123+
color:var(--accent);
1124+
1125+
}
1126+
1127+
.hero-nav-btn.active{
1128+
1129+
color:var(--accent);
1130+
1131+
}
1132+
1133+
.hero-nav-btn::after{
1134+
1135+
content:"";
1136+
1137+
position:absolute;
1138+
1139+
left:0;
1140+
1141+
bottom:-8px;
1142+
1143+
width:100%;
1144+
1145+
height:3px;
1146+
1147+
background:var(--accent);
1148+
1149+
border-radius:999px;
1150+
1151+
transform:scaleX(0);
1152+
1153+
transition:.25s;
1154+
1155+
}
1156+
1157+
.hero-nav-btn.active::after{
1158+
1159+
transform:scaleX(1);
1160+
1161+
}
1162+
1163+
html[data-theme="dark"] .hero-category-nav{
1164+
1165+
background:rgba(28,28,28,.65);
1166+
1167+
border:1px solid rgba(255,255,255,.08);
1168+
1169+
box-shadow:0 10px 35px rgba(0,0,0,.45);
1170+
1171+
}
1172+
1173+
1174+
1175+
1176+
10541177
/* ── Hero Big Logo & Brand Title ────────────────────────────── */
10551178
.hero-logo-header {
10561179
display: flex;
@@ -1834,12 +1957,24 @@ main>.hero-section:has(.hero-code-snippets) {
18341957
align-items: center;
18351958
gap: 8px;
18361959
}
1837-
1838-
.editor-actions {
1960+
.editor-actions,
1961+
.console-actions {
18391962
display: flex;
18401963
gap: 6px;
18411964
}
18421965

1966+
.btn-panel-action.copy-success {
1967+
background: rgba(16, 185, 129, 0.12) !important;
1968+
border-color: rgba(16, 185, 129, 0.4) !important;
1969+
color: #10b981 !important;
1970+
}
1971+
1972+
.btn-panel-action.copy-error {
1973+
background: rgba(239, 68, 68, 0.12) !important;
1974+
border-color: rgba(239, 68, 68, 0.4) !important;
1975+
color: #ef4444 !important;
1976+
}
1977+
18431978
.draft-selector {
18441979
background: var(--bg-glass);
18451980
border: 1px solid var(--border);

0 commit comments

Comments
 (0)