gokaymeydan commited on
Commit
5c8f53e
·
verified ·
1 Parent(s): 7e4feb9

Upload 12 files

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ demo1.gif filter=lfs diff=lfs merge=lfs -text
2
+ demo2.gif filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY requirements.txt .
10
+
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ COPY . .
14
+
15
+ EXPOSE 8501
16
+
17
+ CMD ["streamlit","run","app.py","--server.port=8501","--server.address=0.0.0.0"]
README.md CHANGED
@@ -1,28 +1,48 @@
1
- ---
2
- title: Sorting Algorithm Visualizer
3
- emoji: 📊
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: streamlit
7
- sdk_version: "1.32.2"
8
- app_file: app.py
9
- pinned: false
10
- ---
11
 
12
- # Sorting Algorithm Visualizer
13
-
14
- A Streamlit-based web app that visually compares **Insertion Sort** and **Merge Sort** step-by-step using animated bar charts.
15
 
16
  ## Features
 
 
 
 
 
 
 
 
 
 
 
17
 
18
- - Generates a random array for sorting
19
- - Side-by-side animated visualization of Insertion Sort and Merge Sort
20
- - Highlights active elements and sorted regions
21
- - Displays total number of sorting steps
22
- - Brief algorithm summaries
23
 
24
- ## How to Run Locally
25
 
 
26
  ```bash
 
 
27
  pip install -r requirements.txt
28
- streamlit run app.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sorting Algorithm Visualizer & Analyzer
 
 
 
 
 
 
 
 
 
2
 
3
+ An interactive Streamlit application to **visualize, analyze, and compare sorting algorithms**.
4
+ The project is inspired by *Introduction to Algorithms (CLRS)* and extended with modern visualization and benchmarking features.
 
5
 
6
  ## Features
7
+ - Step-by-step visualization with bubble chart animations.
8
+ - Supported algorithms: Insertion Sort, Merge Sort, Quick Sort, Heap Sort, Counting Sort, Radix Sort (LSD), Shell Sort, Bucket Sort
9
+ - Metrics tracking:
10
+ - Execution time (ms)
11
+ - Comparisons
12
+ - Moves (writes/swaps)
13
+ - Number of frames
14
+ - Sorted OK check
15
+ - Scaling benchmark: analyze performance as input size increases.
16
+ - Export results as CSV.
17
+
18
 
19
+ ![Demo GIF 1](demo1.gif)
20
+ ![Demo GIF 2](demo2.gif)
 
 
 
21
 
 
22
 
23
+ ## Installation
24
  ```bash
25
+ git clone https://github.com/your-username/sorting-algorithm-visualizer.git
26
+ cd sorting-algorithm-visualizer
27
  pip install -r requirements.txt
28
+ ```
29
+
30
+ ## Usage
31
+ Run the Streamlit app:
32
+ ```bash
33
+ streamlit run app.py
34
+ ```
35
+ Open the app in your browser at `http://localhost:8501`.
36
+
37
+ ## Run with Docker
38
+ Build the image:
39
+ ```bash
40
+ docker build -t sorting-algorithm-visualizer .
41
+ ```
42
+
43
+ Run the container:
44
+ ```bash
45
+ docker run -p 8501:8501 sorting-algorithm-visualizer
46
+ ```
47
+
48
+ Open the app in your browser at `http://localhost:8501`.
__pycache__/algorithms.cpython-311.pyc ADDED
Binary file (935 Bytes). View file
 
__pycache__/algorithms.cpython-312.pyc ADDED
Binary file (13.1 kB). View file
 
algorithms.py CHANGED
@@ -1,63 +1,420 @@
1
- def insertion_sort(arr):
2
- steps = [] # keep all steps here
3
- a = arr.copy()
 
 
 
 
 
 
 
 
4
 
 
 
 
 
 
 
5
  for i in range(1, len(a)):
6
  key = a[i]
7
  j = i - 1
8
-
9
- while j >= 0 and a[j] > key:
10
- a[j + 1] = a[j]
11
- steps.append(
12
- {"array": a.copy(), "active_index": j + 1, "sorted_boundary": i}
13
- )
14
- j -= 1
 
 
 
15
  a[j + 1] = key
16
- steps.append(
17
- {"array": a.copy(), "active_index": j + 1, "sorted_boundary": i}
18
- ) # save every moment
19
- return steps
 
20
 
21
 
22
- def merge_sort(arr):
23
- steps = []
24
  a = arr.copy()
25
- merge_sort_recursive(a, 0, len(a) - 1, steps)
26
- return steps
 
 
27
 
28
- def merge_sort_recursive(arr,left,right,steps):
29
- if left < right:
30
- mid = (left + right) // 2
31
- merge_sort_recursive(arr, left, mid, steps)
32
- merge_sort_recursive(arr, mid + 1, right, steps)
33
- merge(arr, left, mid, right, steps)
34
-
35
- def merge(arr, left, mid, right, steps):
36
- left_part = arr[left:mid+1]
37
- right_part = arr[mid + 1:right+1]
38
-
39
- i = j = 0
40
- k = left
41
-
42
- while i < len(left_part) and j < len(right_part):
43
- if left_part[i] <= right_part[j]:
44
- arr[k] = left_part[i]
45
- steps.append({"array": arr.copy(), "active_index":k, "sorted_boundary": right})
 
 
 
 
 
 
 
 
 
 
46
  i += 1
47
- else:
48
- arr[k] = right_part[j]
49
- steps.append({"array": arr.copy(), "active_index":k, "sorted_boundary": right})
 
 
 
 
50
  j += 1
51
- k += 1
52
-
53
- while i < len(left_part):
54
- arr[k] = left_part[i]
55
- steps.append({"array": arr.copy(), "active_index":k, "sorted_boundary": right})
56
- i += 1
57
- k += 1
58
-
59
- while j < len(right_part):
60
- arr[k] = right_part[j]
61
- steps.append({"array": arr.copy(), "active_index":k, "sorted_boundary": right})
62
- j += 1
63
- k += 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # algorithms.py with metrics
2
+ import math
3
+ import time
4
+
5
+
6
+ def _snap(steps, record_steps, a, active_i, boundary=-1):
7
+ if record_steps:
8
+ steps.append(
9
+ {"array": a.copy(), "active_index": active_i, "sorted_boundary": boundary}
10
+ )
11
+
12
 
13
+ def insertion_sort(arr, record_steps: bool = True):
14
+ a = arr.copy()
15
+ steps = []
16
+ comparisons = 0
17
+ moves = 0
18
+ start = time.perf_counter()
19
  for i in range(1, len(a)):
20
  key = a[i]
21
  j = i - 1
22
+ # at least one comparison if entering the while loop
23
+ while j >= 0:
24
+ comparisons += 1
25
+ if a[j] > key:
26
+ a[j + 1] = a[j]
27
+ moves += 1
28
+ _snap(steps, record_steps, a, j + 1, i)
29
+ j -= 1
30
+ else:
31
+ break
32
  a[j + 1] = key
33
+ moves += 1
34
+ _snap(steps, record_steps, a, j + 1, i)
35
+ end = time.perf_counter()
36
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": end - start}
37
+ return steps, metrics
38
 
39
 
40
+ def merge_sort(arr, record_steps: bool = True):
 
41
  a = arr.copy()
42
+ steps = []
43
+ comparisons = 0
44
+ moves = 0
45
+ start = time.perf_counter()
46
 
47
+ def merge(left, mid, right):
48
+ nonlocal comparisons, moves
49
+ # Create copies of subarrays
50
+ left_part = a[left : mid + 1]
51
+ right_part = a[mid + 1 : right + 1]
52
+ i = j = 0
53
+ k = left
54
+
55
+ # Merge while both parts have elements
56
+ while i < len(left_part) and j < len(right_part):
57
+ comparisons += 1 # one comparison each loop
58
+ if left_part[i] <= right_part[j]:
59
+ a[k] = left_part[i]
60
+ moves += 1
61
+ _snap(steps, record_steps, a, k, right)
62
+ i += 1
63
+ else:
64
+ a[k] = right_part[j]
65
+ moves += 1
66
+ _snap(steps, record_steps, a, k, right)
67
+ j += 1
68
+ k += 1
69
+
70
+ # Copy remaining elements of left_part
71
+ while i < len(left_part):
72
+ a[k] = left_part[i]
73
+ moves += 1
74
+ _snap(steps, record_steps, a, k, right)
75
  i += 1
76
+ k += 1
77
+
78
+ # Copy remaining elements of right_part
79
+ while j < len(right_part):
80
+ a[k] = right_part[j]
81
+ moves += 1
82
+ _snap(steps, record_steps, a, k, right)
83
  j += 1
84
+ k += 1
85
+
86
+ def sort(left, right):
87
+ if left >= right:
88
+ return
89
+ mid = (left + right) // 2
90
+ sort(left, mid)
91
+ sort(mid + 1, right)
92
+ merge(left, mid, right)
93
+
94
+ if len(a) > 0:
95
+ sort(0, len(a) - 1)
96
+
97
+ end = time.perf_counter()
98
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": end - start}
99
+ return steps, metrics
100
+
101
+
102
+ def quick_sort(arr, record_steps: bool = True):
103
+ a = arr.copy()
104
+ steps = []
105
+ comparisons = 0
106
+ moves = 0
107
+
108
+ def swap(i, j, *, active_i=None, sorted_b=None):
109
+ nonlocal moves
110
+ if i == j:
111
+ return
112
+ (
113
+ a[i],
114
+ a[j],
115
+ ) = (
116
+ a[j],
117
+ a[i],
118
+ )
119
+ moves += 2
120
+ _snap(
121
+ steps,
122
+ record_steps,
123
+ a,
124
+ i if active_i is None else active_i,
125
+ -1 if sorted_b is None else sorted_b,
126
+ )
127
+
128
+ def partition(low, high):
129
+ nonlocal comparisons, moves
130
+ pivot = a[high]
131
+ i = low - 1
132
+ # compare high-1 and pivot
133
+ for j in range(low, high):
134
+ comparisons += 1
135
+ if a[j] <= pivot:
136
+ i += 1
137
+ swap(i, j, active_i=j)
138
+ # put pivot back to place
139
+ swap(i + 1, high, active_i=i + 1, sorted_b=i + 1)
140
+ return i + 1
141
+
142
+ def qs(low, high):
143
+ if low < high:
144
+ p = partition(low, high)
145
+ qs(low, p - 1)
146
+ qs(p + 1, high)
147
+
148
+ start = time.perf_counter()
149
+ if a:
150
+ qs(0, len(a) - 1)
151
+ seconds = time.perf_counter() - start
152
+
153
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": seconds}
154
+ return steps, metrics
155
+
156
+
157
+ def counting_sort(arr, k=None, record_steps: bool = True):
158
+ a = arr.copy()
159
+ steps = []
160
+ comparisons = 0
161
+ moves = 0
162
+ if not a:
163
+ return steps, {"comparisons": 0, "moves": 0, "seconds": 0.0}
164
+
165
+ # negatifleri desteklemiyorsak koruma (istersen offset ile destekleyebilirsin)
166
+ if min(a) < 0:
167
+ raise ValueError("Counting Sort: negatif değerler desteklenmiyor.")
168
+
169
+ # k (değer aralığı) verilmediyse max+1 al
170
+ if k is None:
171
+ k = max(a) + 1
172
+
173
+ start = time.perf_counter()
174
+
175
+ count = [0] * k
176
+ for v in a:
177
+ count[v] += 1
178
+ for i in range(1, k):
179
+ count[i] += count[i - 1]
180
+
181
+ out = [0] * len(a)
182
+ for v in reversed(a):
183
+ count[v] -= 1
184
+ out[count[v]] = v
185
+
186
+ for i, v in enumerate(out):
187
+ a[i] = v
188
+ moves += 1
189
+ _snap(steps, record_steps, a, i, i)
190
+
191
+ seconds = time.perf_counter() - start
192
+ return steps, {"comparisons": comparisons, "moves": moves, "seconds": seconds}
193
+
194
+
195
+ def radix_sort_lsd(arr, base=10, record_steps: bool = True):
196
+ a = arr.copy()
197
+ steps = []
198
+ comparisons = 0
199
+ moves = 0
200
+
201
+ if not a:
202
+ return steps, {"comparisons": 0, "moves": 0, "seconds": 0.0}
203
+ if base < 2:
204
+ raise ValueError("radix base must be >= 2")
205
+
206
+ start = time.perf_counter()
207
+
208
+ def digit(x, exp):
209
+ return (x // exp) % base
210
+
211
+ exp = 1
212
+ maxv = max(a)
213
+
214
+ # for each digit place
215
+ while maxv // exp > 0:
216
+ # stable counting sort by current digit
217
+ count = [0] * base
218
+
219
+ # count
220
+ for v in a:
221
+ d = digit(v, exp)
222
+ count[d] += 1
223
+
224
+ # prefix sums
225
+ for i in range(1, base):
226
+ count[i] += count[i - 1]
227
+
228
+ # build output(scan from right)
229
+ out = [0] * len(a)
230
+ for i in range(len(a) - 1, -1, -1):
231
+ v = a[i]
232
+ d = digit(v, exp)
233
+ count[d] -= 1
234
+ out[count[d]] = v
235
+
236
+ for i, v in enumerate(out):
237
+ a[i] = v
238
+ moves += 1
239
+ _snap(steps, record_steps, a, i, i)
240
+ exp *= base
241
+
242
+ seconds = time.perf_counter() - start
243
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": seconds}
244
+ return steps, metrics
245
+
246
+
247
+ # --- Heap Sort (max-heap, in-place) with metrics & step recording ---
248
+ # steps: her adımda {"array": a.copy(), "active_index": i, "sorted_boundary": b}
249
+ # - active_index: o karede yeni yazılan / swap’e giren indeks
250
+ # - sorted_boundary: heapsort’ta sıralı kuyruk (suffix) başlangıcı; j >= boundary -> "sorted"
251
+ # metrics:
252
+ # - comparisons: her a[l] > a[m] veya a[r] > a[m] kontrolü 1 karşılaştırma
253
+ # - moves: diziye her yazma 1; swap 2 move
254
+ def heap_sort(arr, record_steps: bool = True):
255
+ a = arr.copy()
256
+ steps = []
257
+ comparisons = 0
258
+ moves = 0
259
+
260
+ heap_sorted = -1
261
+
262
+ def snapshot(active_i, boundary):
263
+ _snap(steps, record_steps, a, active_i, boundary)
264
+
265
+ def swap(i, j):
266
+ nonlocal moves
267
+ if i == j:
268
+ return
269
+ a[i], a[j] = a[j], a[i]
270
+ moves += 2
271
+ snapshot(i, heap_sorted)
272
+
273
+ def heapify(n, i):
274
+ nonlocal comparisons
275
+ while True:
276
+ largest = i
277
+ l = 2 * i + 1
278
+ r = 2 * i + 2
279
+
280
+ if l < n:
281
+ comparisons += 1
282
+ if a[l] > a[largest]:
283
+ largest = l
284
+ if r < n:
285
+ comparisons += 1
286
+ if a[r] > a[largest]:
287
+ largest = r
288
+ if largest == i:
289
+ break
290
+
291
+ swap(i, largest)
292
+ i = largest
293
+
294
+ start = time.perf_counter()
295
+
296
+ n = len(a)
297
+ if n <= 1:
298
+ metrics = {"comparisons": 0, "moves": 0, "seconds": 0.0}
299
+ return steps, metrics
300
+
301
+ # build max heap
302
+ for i in range(n // 2 - 1, -1, -1):
303
+ heapify(n, i)
304
+ # extract max
305
+ for end in range(n - 1, 0, -1):
306
+ swap(0, end)
307
+ heap_sorted = end
308
+ heapify(end, 0)
309
+
310
+ seconds = time.perf_counter() - start
311
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": seconds}
312
+ return steps, metrics
313
+
314
+
315
+ def shell_sort(arr, record_steps: bool = True):
316
+ a = arr.copy()
317
+ steps = []
318
+ comparisons = 0
319
+ moves = 0
320
+
321
+ def snap(active_i, boundary=-1):
322
+ _snap(steps, record_steps, a, active_i, boundary)
323
+
324
+ start = time.perf_counter()
325
+
326
+ n = len(a)
327
+ if n <= 1:
328
+ return steps, {"comparisons": 0, "moves": 0, "seconds": 0.0}
329
+
330
+ gap = n // 2
331
+ while gap > 0:
332
+ for i in range(gap, n):
333
+ key = a[i]
334
+ j = i - gap
335
+
336
+ while j >= 0:
337
+ comparisons += 1
338
+ if a[j] > key:
339
+ a[j + gap] = a[j]
340
+ moves += 1
341
+ snap(j + gap)
342
+ j -= gap
343
+ else:
344
+ break
345
+ a[j + gap] = key
346
+ moves += 1
347
+ snap(j + gap)
348
+
349
+ gap //= 2
350
+
351
+ seconds = time.perf_counter() - start
352
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": seconds}
353
+ return steps, metrics
354
+
355
+
356
+ def bucket_sort(arr, num_buckets=None, record_steps: bool = True):
357
+ a = arr.copy()
358
+ steps = []
359
+ comparisons = 0
360
+ moves = 0
361
+
362
+ if not a:
363
+ return steps, {"comparisons": 0, "moves": 0, "seconds": 0.0}
364
+
365
+ n = len(a)
366
+
367
+ if num_buckets is None:
368
+ num_buckets = max(1, int(math.sqrt(n)))
369
+
370
+ start = time.perf_counter()
371
+
372
+ maxv = max(a)
373
+
374
+ # normlize values range between 0 and 1
375
+ if maxv == 0:
376
+ seconds = time.perf_counter() - start
377
+
378
+ for i, v in enumerate(a):
379
+ _snap(steps, record_steps, a, i, i)
380
+ return steps, {"comparisons": 0, "moves": 0, "seconds": seconds}
381
+
382
+ normalized = [x / (maxv + 1.0) for x in a]
383
+
384
+ # create buckets
385
+ buckets = [[] for _ in range(num_buckets)]
386
+
387
+ # split values to buckets
388
+ for v_norm, v_orig in zip(normalized, a):
389
+ idx = int(v_norm * num_buckets)
390
+ if idx >= num_buckets:
391
+ idx = num_buckets - 1
392
+ buckets[idx].append(v_orig)
393
+
394
+ # sort buckets
395
+
396
+ for b in buckets:
397
+ for i in range(1, len(b)):
398
+ key = b[i]
399
+ j = i - 1
400
+ while j >= 0:
401
+ comparisons += 1
402
+ if b[j] > key:
403
+ b[j + 1] = b[j]
404
+ j -= 1
405
+ else:
406
+ break
407
+ b[j + 1] = key
408
+
409
+ # save at main
410
+ write_i = 0
411
+ for b in buckets:
412
+ for v in b:
413
+ a[write_i] = v
414
+ moves += 1
415
+ _snap(steps, record_steps, a, write_i, write_i)
416
+ write_i += 1
417
+
418
+ seconds = time.perf_counter() - start
419
+ metrics = {"comparisons": comparisons, "moves": moves, "seconds": seconds}
420
+ return steps, metrics
app.py CHANGED
@@ -1,39 +1,222 @@
1
  import random
2
- import plotly.graph_objects as go
 
 
3
  import matplotlib.pyplot as plt
 
 
4
  import streamlit as st
5
- import time
6
 
7
- from algorithms import insertion_sort, merge_sort
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- st.title("Insertion Sort vs Merge Sort Visualizer")
10
 
11
- # --- Veri Girişi ---
12
  st.subheader("Input Configuration")
13
  length = st.slider("List length", 5, 20, 8)
14
- data = random.sample(range(1, 30), length)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  st.write(f"Input array: {data}")
16
 
17
- if st.button("Compare Insertion Sort vs Merge Sort"):
18
- data_insertion = data.copy()
19
- data_merge = data.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- steps_insertion = insertion_sort(data_insertion)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- steps_merge = merge_sort(data_merge)
24
 
25
- st.subheader("Comparison Results")
26
- col1, col2 = st.columns(2)
 
 
27
 
28
- with col1:
29
- st.markdown("### Insertion Sort")
30
- st.write(f"Total steps: {len(steps_insertion)}")
 
 
 
 
 
 
31
 
32
- with col2:
33
- st.markdown("### Merge Sort")
34
- st.write(f"Total steps: {len(steps_merge)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def create_animation(steps, title, color_fn):
 
 
 
 
 
 
 
 
 
 
 
37
  frames = []
38
  for i, step in enumerate(steps):
39
  array = step["array"]
@@ -42,90 +225,289 @@ if st.button("Compare Insertion Sort vs Merge Sort"):
42
 
43
  colors = color_fn(len(array), active_index, sorted_boundary)
44
 
45
- frames.append(go.Frame(
46
- data=[go.Scatter(
47
- x=list(range(len(array))),
48
- y=array,
49
- mode="markers+text",
50
- marker=dict(size=40, color=colors),
51
- text=array,
52
- textposition="middle center"
53
- )],
54
- name=f"Step {i+1}"
55
- ))
 
 
 
 
56
 
57
  initial = steps[0]
58
- initial_colors = color_fn(len(initial["array"]),
59
- initial.get("active_index", -1),
60
- initial.get("sorted_boundary", -1))
 
 
61
 
62
  fig = go.Figure(
63
- data=[go.Scatter(
64
- x=list(range(len(initial["array"]))),
65
- y=initial["array"],
66
- mode="markers+text",
67
- marker=dict(size=40, color=initial_colors),
68
- text=initial["array"],
69
- textposition="middle center"
70
- )],
 
 
71
  layout=go.Layout(
72
- height=400,
 
73
  title=title,
74
  xaxis=dict(range=[-0.5, len(initial["array"]) - 0.5]),
75
- yaxis=dict(range=[0, max(max(s['array']) for s in steps) + 5]),
76
- updatemenus=[dict(
77
- type="buttons",
78
- buttons=[dict(label="Play", method="animate", args=[None])],
79
- showactive=False
80
- )],
81
- sliders=[{
82
- "steps": [{
83
- "args": [[f"Step {i+1}"], {"frame": {"duration": 500, "redraw": True}}],
84
- "label": f"{i+1}",
85
- "method": "animate"
86
- } for i in range(len(frames))],
87
- "transition": {"duration": 0},
88
- "x": 0, "y": -0.1,
89
- "currentvalue": {"prefix": "Step: "}
90
- }]
 
 
 
 
 
 
 
 
 
 
 
91
  ),
92
- frames=frames
93
  )
94
  return fig
95
 
96
  def insertion_colors(length, active_index, sorted_boundary):
97
  return [
98
- "red" if j == active_index else
99
- "green" if j <= sorted_boundary else "gray"
 
 
 
100
  for j in range(length)
101
  ]
102
 
103
  def merge_colors(length, active_index, sorted_boundary):
104
  return [
105
- "purple" if j == active_index else
106
- "blue" if j <= sorted_boundary else "gray"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  for j in range(length)
108
  ]
109
 
110
- st.plotly_chart(create_animation(steps_insertion, "Insertion Sort", insertion_colors))
111
-
112
- st.plotly_chart(create_animation(steps_merge, "Merge Sort", merge_colors))
113
-
114
- st.subheader("About the Algorithms")
115
- st.markdown("**Insertion Sort**")
116
- st.markdown("""
117
- - Simple comparison-based sorting algorithm
118
- - Builds the sorted array one element at a time
119
- - In-place and stable
120
- - Best case: Θ(n) when the array is already sorted
121
- - Worst case: Θ(n²) when the array is reverse sorted
122
- """)
123
-
124
- st.markdown("**Merge Sort**")
125
- st.markdown("""
126
- - Divide-and-conquer algorithm
127
- - Recursively splits the array and merges sorted halves
128
- - Stable but not in-place
129
- - Consistently performs in Θ(n log n) time in all cases
130
- - Requires additional memory for merging
131
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import random
2
+ import statistics
3
+ import time
4
+
5
  import matplotlib.pyplot as plt
6
+ import pandas as pd
7
+ import plotly.graph_objects as go
8
  import streamlit as st
 
9
 
10
+ import algorithms as alg
11
+
12
+ st.title("Sorting Algorithm Visualizer")
13
+
14
+
15
+ def render_metrics(m):
16
+ if not m:
17
+ return
18
+ ms = m.get("seconds", 0.0) * 1000.0
19
+ comps = m.get("comparisons", 0)
20
+ moves = m.get("moves", 0)
21
+ c1, c2, c3 = st.columns(3)
22
+ with c1:
23
+ st.metric("Time (ms)", f"{ms:.2f}")
24
+ with c2:
25
+ st.metric("Comparisons", f"{comps:,}")
26
+ with c3:
27
+ st.metric("Moves", f"{moves:,}")
28
 
 
29
 
 
30
  st.subheader("Input Configuration")
31
  length = st.slider("List length", 5, 20, 8)
32
+ # Input type selector
33
+ input_type = st.selectbox(
34
+ "Input type", ["Random", "Sorted", "Reversed", "Few unique"], index=0
35
+ )
36
+ # Generate data based on input type
37
+ if input_type == "Random":
38
+ data = random.sample(range(1, 30), length) # unique values
39
+ elif input_type == "Sorted":
40
+ data = sorted(random.sample(range(1, 30), length))
41
+ elif input_type == "Reversed":
42
+ data = sorted(random.sample(range(1, 30), length), reverse=True)
43
+ else: # Few unique
44
+ pool = random.sample(range(1, 30), k=min(3, max(1, length // 3)))
45
+ data = [random.choice(pool) for _ in range(length)]
46
+ random.shuffle(data)
47
+
48
  st.write(f"Input array: {data}")
49
 
50
+ st.subheader("Scaling Benchmark (n -> time)")
51
+ sizes_str = st.text_input("Sizes", value="10, 20, 40, 80, 160")
52
+ try:
53
+ sizes = [int(x.strip()) for x in sizes_str.split(",") if x.strip()]
54
+ sizes = [s for s in sizes if s > 0]
55
+ except Exception:
56
+ sizes = []
57
+
58
+ runs_scale = st.slider("Runs per size", 3, 50, 10, step=1)
59
+
60
+ algo_options = [
61
+ "Insertion Sort",
62
+ "Merge Sort",
63
+ "Quick Sort",
64
+ "Counting Sort",
65
+ "Radix Sort (LSD)",
66
+ "Heap Sort",
67
+ "Shell Sort",
68
+ "Bucket Sort",
69
+ ]
70
+ selected_algos = st.multiselect(
71
+ "Algorithms to include",
72
+ options=algo_options,
73
+ default=[
74
+ "Insertion Sort",
75
+ "Merge Sort",
76
+ "Quick Sort",
77
+ "Counting Sort",
78
+ "Radix Sort (LSD)",
79
+ "Heap Sort",
80
+ "Shell Sort",
81
+ "Bucket Sort",
82
+ ],
83
+ )
84
+
85
+
86
+ def make_data(n: int, input_type: str):
87
+ if input_type == "Random":
88
+ return [random.randint(1, max(30, n * 3)) for _ in range(n)]
89
+ elif input_type == "Sorted":
90
+ arr = [random.randint(1, max(30, n * 3)) for _ in range(n)]
91
+ return sorted(arr)
92
+ elif input_type == "Reversed":
93
+ arr = [random.randint(1, max(30, n * 3)) for _ in range(n)]
94
+ return sorted(arr, reverse=True)
95
+ else:
96
+ pool_size = max(2, min(10, n // 5))
97
+ pool = [random.randint(1, max(30, n * 3)) for _ in range(pool_size)]
98
+ return [random.choice(pool) for _ in range(n)]
99
+
100
 
101
+ ## cal algo
102
+ def get_algo_fn(name: str):
103
+ if name == "Insertion Sort":
104
+ return lambda arr: alg.insertion_sort(arr, record_steps=False)
105
+ if name == "Merge Sort":
106
+ return lambda arr: alg.merge_sort(arr, record_steps=False)
107
+ if name == "Quick Sort":
108
+ return lambda arr: alg.quick_sort(arr, record_steps=False)
109
+ if name == "Counting Sort":
110
+ return lambda arr: alg.counting_sort(arr, record_steps=False)
111
+ if name == "Radix Sort (LSD)":
112
+ return lambda arr: alg.radix_sort_lsd(arr, base=10, record_steps=False)
113
+ if name == "Heap Sort":
114
+ return lambda arr: alg.heap_sort(arr, record_steps=False)
115
+ if name == "Shell Sort":
116
+ return lambda arr: alg.shell_sort(arr, record_steps=False)
117
+ if name == "Bucket Sort":
118
+ return lambda arr: alg.bucket_sort(arr, record_steps=False)
119
+ raise ValueError(name)
120
 
 
121
 
122
+ if st.button("Run Scaling Benchmark"):
123
+ if not sizes:
124
+ st.error("Please enter at least one valid size (e.g., 10, 20, 40).")
125
+ st.stop()
126
 
127
+ rows = []
128
+ for n in sizes:
129
+ for algo_name in selected_algos:
130
+ fn = get_algo_fn(algo_name)
131
+ times = []
132
+ for _ in range(runs_scale):
133
+ arr = make_data(n, input_type)
134
+ _, m = fn(arr)
135
+ times.append(m["seconds"] * 1000.0) # ms
136
 
137
+ avg_ms = statistics.mean(times)
138
+ std_ms = statistics.pstdev(times) if len(times) > 1 else 0.0
139
+
140
+ rows.append(
141
+ {
142
+ "n": n,
143
+ "Algorithm": algo_name,
144
+ "Average Time (ms)": avg_ms,
145
+ "Std Dev (ms)": std_ms,
146
+ }
147
+ )
148
+
149
+ df_scale = pd.DataFrame(rows)
150
+ fig_scale = go.Figure()
151
+
152
+ for algo_name in selected_algos:
153
+ sub = df_scale[df_scale["Algorithm"] == algo_name].sort_values("n")
154
+ fig_scale.add_trace(
155
+ go.Scatter(
156
+ x=sub["n"],
157
+ y=sub["Average Time (ms)"],
158
+ mode="lines+markers",
159
+ name=algo_name,
160
+ )
161
+ )
162
+ fig_scale.update_layout(
163
+ title=f"Scaling Benchmark (input_type = {input_type}, runs = {runs_scale})",
164
+ xaxis_title="n (input size)",
165
+ yaxis_title="Average Time (ms)",
166
+ height=480,
167
+ width=1000,
168
+ )
169
+ st.plotly_chart(fig_scale, use_container_width=True)
170
+
171
+ st.dataframe(
172
+ df_scale.sort_values(["Algorithm", "n"]).style.format(
173
+ {
174
+ "Average Time (ms)": "{:.3f}",
175
+ "Std Dev (ms)": "{:.3f}",
176
+ }
177
+ ),
178
+ use_container_width=True,
179
+ )
180
+
181
+ st.download_button(
182
+ "Download Scaling CSV",
183
+ data=df_scale.to_csv(index=False).encode("utf-8"),
184
+ file_name="scaling_benchmark.csv",
185
+ mime="text/csv",
186
+ )
187
+
188
+ if st.button("Run Comparison"):
189
+
190
+ data_insertion = data.copy()
191
+ data_merge = data.copy()
192
+ data_quick = data.copy()
193
+ data_counting = data.copy()
194
+ data_radix = data.copy()
195
+ data_heap = data.copy()
196
+ data_shell = data.copy()
197
+ data_bucket = data.copy()
198
+
199
+ steps_insertion, metrics_insertion = alg.insertion_sort(data_insertion)
200
+ steps_merge, metrics_merge = alg.merge_sort(data_merge)
201
+ steps_quick, metrics_quick = alg.quick_sort(data_quick)
202
+ steps_counting, metrics_counting = alg.counting_sort(data_counting)
203
+ steps_radix, metrics_radix = alg.radix_sort_lsd(data_radix, base=10)
204
+ steps_heap, metrics_heap = alg.heap_sort(data_heap)
205
+ steps_shell, metrics_shell = alg.shell_sort(data_shell)
206
+ steps_bucket, metrics_bucket = alg.bucket_sort(data_bucket)
207
 
208
  def create_animation(steps, title, color_fn):
209
+ if not steps:
210
+ return go.Figure(
211
+ layout=go.Layout(
212
+ width=900,
213
+ height=420,
214
+ title=title,
215
+ xaxis=dict(visible=False),
216
+ yaxis=dict(visible=False),
217
+ annotations=[dict(text="No steps to display", showarrow=False)],
218
+ )
219
+ )
220
  frames = []
221
  for i, step in enumerate(steps):
222
  array = step["array"]
 
225
 
226
  colors = color_fn(len(array), active_index, sorted_boundary)
227
 
228
+ frames.append(
229
+ go.Frame(
230
+ data=[
231
+ go.Scatter(
232
+ x=list(range(len(array))),
233
+ y=array,
234
+ mode="markers+text",
235
+ marker=dict(size=28, color=colors),
236
+ text=array,
237
+ textposition="middle center",
238
+ )
239
+ ],
240
+ name=f"Step {i+1}",
241
+ )
242
+ )
243
 
244
  initial = steps[0]
245
+ initial_colors = color_fn(
246
+ len(initial["array"]),
247
+ initial.get("active_index", -1),
248
+ initial.get("sorted_boundary", -1),
249
+ )
250
 
251
  fig = go.Figure(
252
+ data=[
253
+ go.Scatter(
254
+ x=list(range(len(initial["array"]))),
255
+ y=initial["array"],
256
+ mode="markers+text",
257
+ marker=dict(size=28, color=initial_colors),
258
+ text=initial["array"],
259
+ textposition="middle center",
260
+ )
261
+ ],
262
  layout=go.Layout(
263
+ width=900,
264
+ height=420,
265
  title=title,
266
  xaxis=dict(range=[-0.5, len(initial["array"]) - 0.5]),
267
+ yaxis=dict(range=[0, max(max(s["array"]) for s in steps) + 5]),
268
+ updatemenus=[
269
+ dict(
270
+ type="buttons",
271
+ buttons=[dict(label="Play", method="animate", args=[None])],
272
+ showactive=False,
273
+ )
274
+ ],
275
+ sliders=[
276
+ {
277
+ "steps": [
278
+ {
279
+ "args": [
280
+ [f"Step {i+1}"],
281
+ {"frame": {"duration": 500, "redraw": True}},
282
+ ],
283
+ "label": f"{i+1}",
284
+ "method": "animate",
285
+ }
286
+ for i in range(len(frames))
287
+ ],
288
+ "transition": {"duration": 0},
289
+ "x": 0,
290
+ "y": -0.1,
291
+ "currentvalue": {"prefix": "Step: "},
292
+ }
293
+ ],
294
  ),
295
+ frames=frames,
296
  )
297
  return fig
298
 
299
  def insertion_colors(length, active_index, sorted_boundary):
300
  return [
301
+ (
302
+ "red"
303
+ if j == active_index
304
+ else "green" if j <= sorted_boundary else "gray"
305
+ )
306
  for j in range(length)
307
  ]
308
 
309
  def merge_colors(length, active_index, sorted_boundary):
310
  return [
311
+ (
312
+ "purple"
313
+ if j == active_index
314
+ else "blue" if j <= sorted_boundary else "gray"
315
+ )
316
+ for j in range(length)
317
+ ]
318
+
319
+ def quick_colors(length, active_index, sorted_boundary):
320
+ return [
321
+ (
322
+ "orange"
323
+ if j == active_index
324
+ else "green" if j == sorted_boundary else "gray"
325
+ )
326
+ for j in range(length)
327
+ ]
328
+
329
+ def counting_colors(length, active_index, sorted_boundary):
330
+ return [
331
+ (
332
+ "purple"
333
+ if j == active_index
334
+ else "green" if j == sorted_boundary else "gray"
335
+ )
336
+ for j in range(length)
337
+ ]
338
+
339
+ def radix_colors(length, active_index, sorted_boundary):
340
+ return [
341
+ (
342
+ "purple"
343
+ if j == active_index
344
+ else "green" if j <= sorted_boundary else "gray"
345
+ )
346
+ for j in range(length)
347
+ ]
348
+
349
+ def heap_colors(length, active_index, sorted_boundary):
350
+ return [
351
+ (
352
+ "orange"
353
+ if j == active_index
354
+ else (
355
+ "green"
356
+ if (sorted_boundary != -1 and j >= sorted_boundary)
357
+ else "gray"
358
+ )
359
+ )
360
+ for j in range(length)
361
+ ]
362
+
363
+ def shell_colors(length, active_index, sorted_boundary):
364
+ return [("orange" if j == active_index else "gray") for j in range(length)]
365
+
366
+ def bucket_colors(length, active_index, sorted_boundary):
367
+ return [
368
+ (
369
+ "purple"
370
+ if j == active_index
371
+ else "green" if j <= sorted_boundary else "gray"
372
+ )
373
  for j in range(length)
374
  ]
375
 
376
+ (
377
+ tab_ins,
378
+ tab_mer,
379
+ tab_quick,
380
+ tab_count,
381
+ tab_radix,
382
+ tab_heap,
383
+ tab_shell,
384
+ tab_bucket,
385
+ ) = st.tabs(
386
+ [
387
+ "Insertion",
388
+ "Merge",
389
+ "Quick",
390
+ "Counting",
391
+ "Radix (LSD)",
392
+ "Heap",
393
+ "Shell",
394
+ "Bucket",
395
+ ]
396
+ )
397
+
398
+ with tab_ins:
399
+ st.plotly_chart(
400
+ create_animation(steps_insertion, "Insertion Sort", insertion_colors),
401
+ use_container_width=True,
402
+ )
403
+ with tab_mer:
404
+ st.plotly_chart(
405
+ create_animation(steps_merge, "Merge Sort", merge_colors),
406
+ use_container_width=True,
407
+ )
408
+ with tab_quick:
409
+ st.plotly_chart(
410
+ create_animation(steps_quick, "Quick Sort", quick_colors),
411
+ use_container_width=True,
412
+ )
413
+ with tab_count:
414
+ st.plotly_chart(
415
+ create_animation(steps_counting, "Counting Sort", counting_colors),
416
+ use_container_width=True,
417
+ )
418
+ with tab_radix:
419
+ st.plotly_chart(
420
+ create_animation(steps_radix, "Radix Sort (LSD)", radix_colors),
421
+ use_container_width=True,
422
+ )
423
+ with tab_heap:
424
+ st.plotly_chart(
425
+ create_animation(steps_heap, "Heap Sort", heap_colors),
426
+ use_container_width=True,
427
+ )
428
+ with tab_shell:
429
+ st.plotly_chart(
430
+ create_animation(steps_shell, "Shell Sort", shell_colors),
431
+ use_container_width=True,
432
+ )
433
+ with tab_bucket:
434
+ st.plotly_chart(
435
+ create_animation(steps_bucket, "Bucket Sort", bucket_colors),
436
+ use_container_width=True,
437
+ )
438
+
439
+ df = pd.DataFrame(
440
+ [
441
+ {
442
+ "Algorithm": "Insertion Sort",
443
+ "Time (ms)": metrics_insertion["seconds"] * 1000,
444
+ "Comparisons": metrics_insertion["comparisons"],
445
+ "Moves": metrics_insertion["moves"],
446
+ "Frames": len(steps_insertion),
447
+ "Sorted OK": steps_insertion[-1]["array"] == sorted(data),
448
+ },
449
+ {
450
+ "Algorithm": "Merge Sort",
451
+ "Time (ms)": metrics_merge["seconds"] * 1000,
452
+ "Comparisons": metrics_merge["comparisons"],
453
+ "Moves": metrics_merge["moves"],
454
+ "Frames": len(steps_merge),
455
+ "Sorted OK": steps_merge[-1]["array"] == sorted(data),
456
+ },
457
+ {
458
+ "Algorithm": "Quick Sort",
459
+ "Time (ms)": metrics_quick["seconds"] * 1000,
460
+ "Comparisons": metrics_quick["comparisons"],
461
+ "Moves": metrics_quick["moves"],
462
+ "Frames": len(steps_quick),
463
+ "Sorted OK": steps_quick[-1]["array"] == sorted(data),
464
+ },
465
+ {
466
+ "Algorithm": "Counting Sort",
467
+ "Time (ms)": metrics_counting["seconds"] * 1000,
468
+ "Comparisons": metrics_counting["comparisons"],
469
+ "Moves": metrics_counting["moves"],
470
+ "Frames": len(steps_counting),
471
+ "Sorted OK": steps_counting[-1]["array"] == sorted(data),
472
+ },
473
+ {
474
+ "Algorithm": "Radix Sort(LSD)",
475
+ "Time (ms)": metrics_radix["seconds"] * 1000,
476
+ "Comparisons": metrics_radix["comparisons"],
477
+ "Moves": metrics_radix["moves"],
478
+ "Frames": len(steps_radix),
479
+ "Sorted OK": steps_radix[-1]["array"] == sorted(data),
480
+ },
481
+ {
482
+ "Algorithm": "Heap Sort",
483
+ "Time (ms)": metrics_heap["seconds"] * 1000,
484
+ "Comparisons": metrics_heap["comparisons"],
485
+ "Moves": metrics_heap["moves"],
486
+ "Frames": len(steps_heap),
487
+ "Sorted OK": steps_heap[-1]["array"] == sorted(data),
488
+ },
489
+ {
490
+ "Algorithm": "Shell Sort",
491
+ "Time (ms)": metrics_shell["seconds"] * 1000,
492
+ "Comparisons": metrics_shell["comparisons"],
493
+ "Moves": metrics_shell["moves"],
494
+ "Frames": len(steps_shell),
495
+ "Sorted OK": steps_shell[-1]["array"] == sorted(data),
496
+ },
497
+ {
498
+ "Algorithm": "Bucket Sort",
499
+ "Time (ms)": metrics_bucket["seconds"] * 1000,
500
+ "Comparisons": metrics_bucket["comparisons"],
501
+ "Moves": metrics_bucket["moves"],
502
+ "Frames": len(steps_bucket),
503
+ "Sorted OK": steps_bucket[-1]["array"] == sorted(data),
504
+ },
505
+ ]
506
+ )
507
+ st.subheader("Summary Table")
508
+ st.dataframe(df.style.format({"Time (ms)": "{:.2f}"}), use_container_width=True)
509
+
510
+ csv = df.to_csv(index=False).encode("utf-8")
511
+ st.download_button(
512
+ "Download CSV", data=csv, file_name="sorting_summary.csv", mime="text/csv"
513
+ )
demo1.gif ADDED

Git LFS Details

  • SHA256: f0b623376db10e2dc1b218f7a5a74faabad8701b6612652f526cbbb53b274f9f
  • Pointer size: 132 Bytes
  • Size of remote file: 1.36 MB
demo2.gif ADDED

Git LFS Details

  • SHA256: db36f3393fae82fec57d8c15ea231a8e278a47db52bb555507b8c926e72bdcb6
  • Pointer size: 131 Bytes
  • Size of remote file: 309 kB
notes.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # insertion sort
2
+
3
+ def insertion_sort(arr):
4
+ steps = [] # keep all steps here
5
+
6
+ for j in range(1, len(arr)):
7
+ key = arr[j]
8
+ i = j - 1
9
+
10
+ while i >= 0 and arr[i] > key:
11
+ arr[i + 1] = arr[i]
12
+ i -= 1
13
+ steps.append(arr.copy()) # her kaydırma adımını kaydet
14
+ arr[i + 1] = key
15
+ steps.append(arr.copy()) # save every moment
16
+ # arr listesini sıralıyor
17
+ # Ama her küçük değişiklikten sonra o anki halini steps listesine ekliyor
18
+
19
+ return steps
20
+
21
+ # nested listeler için deepcopy()
22
+ # Liste içinde başka listeler varsa, onları da ayrı ayrı kopyalar
23
+
24
+ from algorithms import insertion_sort
25
+
26
+ my_list=[5,2,4,6,1,3]
27
+
28
+ steps = insertion_sort(my_list)
29
+
30
+ for i, step in enumerate(steps):
31
+ print(f"Step {i + 1}: {step}")
32
+
33
+ # enumerate(steps) → Hem adımı (i) hem listeyi (step) aynı anda alır
34
+ # Her sıralama adımı steps içinde kayıtlı olduğu için tüm sıralama sürecini gözlemleyebiliriz
35
+ # Bu yapı daha sonra Streamlit arayüzüne kolayca taşınabilir
36
+
37
+ def print_step(step):
38
+ for num in step:
39
+ bar = '█' * num # Sayıya göre bar uzunluğu
40
+ print(f"{num:>2} {bar}")
41
+ print("-" * 20) # Adımlar arasında ayraç
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
  streamlit>=1.25.0,<2.0.0
2
  plotly>=5.18.0
3
  numpy<=1.26.4
4
- matplotlib
 
1
  streamlit>=1.25.0,<2.0.0
2
  plotly>=5.18.0
3
  numpy<=1.26.4
4
+ matplotlib>=3.7.0