gokaymeydan's picture
Upload 12 files
5c8f53e verified
import random
import statistics
import time
import matplotlib.pyplot as plt
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
import algorithms as alg
st.title("Sorting Algorithm Visualizer")
def render_metrics(m):
if not m:
return
ms = m.get("seconds", 0.0) * 1000.0
comps = m.get("comparisons", 0)
moves = m.get("moves", 0)
c1, c2, c3 = st.columns(3)
with c1:
st.metric("Time (ms)", f"{ms:.2f}")
with c2:
st.metric("Comparisons", f"{comps:,}")
with c3:
st.metric("Moves", f"{moves:,}")
st.subheader("Input Configuration")
length = st.slider("List length", 5, 20, 8)
# Input type selector
input_type = st.selectbox(
"Input type", ["Random", "Sorted", "Reversed", "Few unique"], index=0
)
# Generate data based on input type
if input_type == "Random":
data = random.sample(range(1, 30), length) # unique values
elif input_type == "Sorted":
data = sorted(random.sample(range(1, 30), length))
elif input_type == "Reversed":
data = sorted(random.sample(range(1, 30), length), reverse=True)
else: # Few unique
pool = random.sample(range(1, 30), k=min(3, max(1, length // 3)))
data = [random.choice(pool) for _ in range(length)]
random.shuffle(data)
st.write(f"Input array: {data}")
st.subheader("Scaling Benchmark (n -> time)")
sizes_str = st.text_input("Sizes", value="10, 20, 40, 80, 160")
try:
sizes = [int(x.strip()) for x in sizes_str.split(",") if x.strip()]
sizes = [s for s in sizes if s > 0]
except Exception:
sizes = []
runs_scale = st.slider("Runs per size", 3, 50, 10, step=1)
algo_options = [
"Insertion Sort",
"Merge Sort",
"Quick Sort",
"Counting Sort",
"Radix Sort (LSD)",
"Heap Sort",
"Shell Sort",
"Bucket Sort",
]
selected_algos = st.multiselect(
"Algorithms to include",
options=algo_options,
default=[
"Insertion Sort",
"Merge Sort",
"Quick Sort",
"Counting Sort",
"Radix Sort (LSD)",
"Heap Sort",
"Shell Sort",
"Bucket Sort",
],
)
def make_data(n: int, input_type: str):
if input_type == "Random":
return [random.randint(1, max(30, n * 3)) for _ in range(n)]
elif input_type == "Sorted":
arr = [random.randint(1, max(30, n * 3)) for _ in range(n)]
return sorted(arr)
elif input_type == "Reversed":
arr = [random.randint(1, max(30, n * 3)) for _ in range(n)]
return sorted(arr, reverse=True)
else:
pool_size = max(2, min(10, n // 5))
pool = [random.randint(1, max(30, n * 3)) for _ in range(pool_size)]
return [random.choice(pool) for _ in range(n)]
## cal algo
def get_algo_fn(name: str):
if name == "Insertion Sort":
return lambda arr: alg.insertion_sort(arr, record_steps=False)
if name == "Merge Sort":
return lambda arr: alg.merge_sort(arr, record_steps=False)
if name == "Quick Sort":
return lambda arr: alg.quick_sort(arr, record_steps=False)
if name == "Counting Sort":
return lambda arr: alg.counting_sort(arr, record_steps=False)
if name == "Radix Sort (LSD)":
return lambda arr: alg.radix_sort_lsd(arr, base=10, record_steps=False)
if name == "Heap Sort":
return lambda arr: alg.heap_sort(arr, record_steps=False)
if name == "Shell Sort":
return lambda arr: alg.shell_sort(arr, record_steps=False)
if name == "Bucket Sort":
return lambda arr: alg.bucket_sort(arr, record_steps=False)
raise ValueError(name)
if st.button("Run Scaling Benchmark"):
if not sizes:
st.error("Please enter at least one valid size (e.g., 10, 20, 40).")
st.stop()
rows = []
for n in sizes:
for algo_name in selected_algos:
fn = get_algo_fn(algo_name)
times = []
for _ in range(runs_scale):
arr = make_data(n, input_type)
_, m = fn(arr)
times.append(m["seconds"] * 1000.0) # ms
avg_ms = statistics.mean(times)
std_ms = statistics.pstdev(times) if len(times) > 1 else 0.0
rows.append(
{
"n": n,
"Algorithm": algo_name,
"Average Time (ms)": avg_ms,
"Std Dev (ms)": std_ms,
}
)
df_scale = pd.DataFrame(rows)
fig_scale = go.Figure()
for algo_name in selected_algos:
sub = df_scale[df_scale["Algorithm"] == algo_name].sort_values("n")
fig_scale.add_trace(
go.Scatter(
x=sub["n"],
y=sub["Average Time (ms)"],
mode="lines+markers",
name=algo_name,
)
)
fig_scale.update_layout(
title=f"Scaling Benchmark (input_type = {input_type}, runs = {runs_scale})",
xaxis_title="n (input size)",
yaxis_title="Average Time (ms)",
height=480,
width=1000,
)
st.plotly_chart(fig_scale, use_container_width=True)
st.dataframe(
df_scale.sort_values(["Algorithm", "n"]).style.format(
{
"Average Time (ms)": "{:.3f}",
"Std Dev (ms)": "{:.3f}",
}
),
use_container_width=True,
)
st.download_button(
"Download Scaling CSV",
data=df_scale.to_csv(index=False).encode("utf-8"),
file_name="scaling_benchmark.csv",
mime="text/csv",
)
if st.button("Run Comparison"):
data_insertion = data.copy()
data_merge = data.copy()
data_quick = data.copy()
data_counting = data.copy()
data_radix = data.copy()
data_heap = data.copy()
data_shell = data.copy()
data_bucket = data.copy()
steps_insertion, metrics_insertion = alg.insertion_sort(data_insertion)
steps_merge, metrics_merge = alg.merge_sort(data_merge)
steps_quick, metrics_quick = alg.quick_sort(data_quick)
steps_counting, metrics_counting = alg.counting_sort(data_counting)
steps_radix, metrics_radix = alg.radix_sort_lsd(data_radix, base=10)
steps_heap, metrics_heap = alg.heap_sort(data_heap)
steps_shell, metrics_shell = alg.shell_sort(data_shell)
steps_bucket, metrics_bucket = alg.bucket_sort(data_bucket)
def create_animation(steps, title, color_fn):
if not steps:
return go.Figure(
layout=go.Layout(
width=900,
height=420,
title=title,
xaxis=dict(visible=False),
yaxis=dict(visible=False),
annotations=[dict(text="No steps to display", showarrow=False)],
)
)
frames = []
for i, step in enumerate(steps):
array = step["array"]
active_index = step.get("active_index", -1)
sorted_boundary = step.get("sorted_boundary", -1)
colors = color_fn(len(array), active_index, sorted_boundary)
frames.append(
go.Frame(
data=[
go.Scatter(
x=list(range(len(array))),
y=array,
mode="markers+text",
marker=dict(size=28, color=colors),
text=array,
textposition="middle center",
)
],
name=f"Step {i+1}",
)
)
initial = steps[0]
initial_colors = color_fn(
len(initial["array"]),
initial.get("active_index", -1),
initial.get("sorted_boundary", -1),
)
fig = go.Figure(
data=[
go.Scatter(
x=list(range(len(initial["array"]))),
y=initial["array"],
mode="markers+text",
marker=dict(size=28, color=initial_colors),
text=initial["array"],
textposition="middle center",
)
],
layout=go.Layout(
width=900,
height=420,
title=title,
xaxis=dict(range=[-0.5, len(initial["array"]) - 0.5]),
yaxis=dict(range=[0, max(max(s["array"]) for s in steps) + 5]),
updatemenus=[
dict(
type="buttons",
buttons=[dict(label="Play", method="animate", args=[None])],
showactive=False,
)
],
sliders=[
{
"steps": [
{
"args": [
[f"Step {i+1}"],
{"frame": {"duration": 500, "redraw": True}},
],
"label": f"{i+1}",
"method": "animate",
}
for i in range(len(frames))
],
"transition": {"duration": 0},
"x": 0,
"y": -0.1,
"currentvalue": {"prefix": "Step: "},
}
],
),
frames=frames,
)
return fig
def insertion_colors(length, active_index, sorted_boundary):
return [
(
"red"
if j == active_index
else "green" if j <= sorted_boundary else "gray"
)
for j in range(length)
]
def merge_colors(length, active_index, sorted_boundary):
return [
(
"purple"
if j == active_index
else "blue" if j <= sorted_boundary else "gray"
)
for j in range(length)
]
def quick_colors(length, active_index, sorted_boundary):
return [
(
"orange"
if j == active_index
else "green" if j == sorted_boundary else "gray"
)
for j in range(length)
]
def counting_colors(length, active_index, sorted_boundary):
return [
(
"purple"
if j == active_index
else "green" if j == sorted_boundary else "gray"
)
for j in range(length)
]
def radix_colors(length, active_index, sorted_boundary):
return [
(
"purple"
if j == active_index
else "green" if j <= sorted_boundary else "gray"
)
for j in range(length)
]
def heap_colors(length, active_index, sorted_boundary):
return [
(
"orange"
if j == active_index
else (
"green"
if (sorted_boundary != -1 and j >= sorted_boundary)
else "gray"
)
)
for j in range(length)
]
def shell_colors(length, active_index, sorted_boundary):
return [("orange" if j == active_index else "gray") for j in range(length)]
def bucket_colors(length, active_index, sorted_boundary):
return [
(
"purple"
if j == active_index
else "green" if j <= sorted_boundary else "gray"
)
for j in range(length)
]
(
tab_ins,
tab_mer,
tab_quick,
tab_count,
tab_radix,
tab_heap,
tab_shell,
tab_bucket,
) = st.tabs(
[
"Insertion",
"Merge",
"Quick",
"Counting",
"Radix (LSD)",
"Heap",
"Shell",
"Bucket",
]
)
with tab_ins:
st.plotly_chart(
create_animation(steps_insertion, "Insertion Sort", insertion_colors),
use_container_width=True,
)
with tab_mer:
st.plotly_chart(
create_animation(steps_merge, "Merge Sort", merge_colors),
use_container_width=True,
)
with tab_quick:
st.plotly_chart(
create_animation(steps_quick, "Quick Sort", quick_colors),
use_container_width=True,
)
with tab_count:
st.plotly_chart(
create_animation(steps_counting, "Counting Sort", counting_colors),
use_container_width=True,
)
with tab_radix:
st.plotly_chart(
create_animation(steps_radix, "Radix Sort (LSD)", radix_colors),
use_container_width=True,
)
with tab_heap:
st.plotly_chart(
create_animation(steps_heap, "Heap Sort", heap_colors),
use_container_width=True,
)
with tab_shell:
st.plotly_chart(
create_animation(steps_shell, "Shell Sort", shell_colors),
use_container_width=True,
)
with tab_bucket:
st.plotly_chart(
create_animation(steps_bucket, "Bucket Sort", bucket_colors),
use_container_width=True,
)
df = pd.DataFrame(
[
{
"Algorithm": "Insertion Sort",
"Time (ms)": metrics_insertion["seconds"] * 1000,
"Comparisons": metrics_insertion["comparisons"],
"Moves": metrics_insertion["moves"],
"Frames": len(steps_insertion),
"Sorted OK": steps_insertion[-1]["array"] == sorted(data),
},
{
"Algorithm": "Merge Sort",
"Time (ms)": metrics_merge["seconds"] * 1000,
"Comparisons": metrics_merge["comparisons"],
"Moves": metrics_merge["moves"],
"Frames": len(steps_merge),
"Sorted OK": steps_merge[-1]["array"] == sorted(data),
},
{
"Algorithm": "Quick Sort",
"Time (ms)": metrics_quick["seconds"] * 1000,
"Comparisons": metrics_quick["comparisons"],
"Moves": metrics_quick["moves"],
"Frames": len(steps_quick),
"Sorted OK": steps_quick[-1]["array"] == sorted(data),
},
{
"Algorithm": "Counting Sort",
"Time (ms)": metrics_counting["seconds"] * 1000,
"Comparisons": metrics_counting["comparisons"],
"Moves": metrics_counting["moves"],
"Frames": len(steps_counting),
"Sorted OK": steps_counting[-1]["array"] == sorted(data),
},
{
"Algorithm": "Radix Sort(LSD)",
"Time (ms)": metrics_radix["seconds"] * 1000,
"Comparisons": metrics_radix["comparisons"],
"Moves": metrics_radix["moves"],
"Frames": len(steps_radix),
"Sorted OK": steps_radix[-1]["array"] == sorted(data),
},
{
"Algorithm": "Heap Sort",
"Time (ms)": metrics_heap["seconds"] * 1000,
"Comparisons": metrics_heap["comparisons"],
"Moves": metrics_heap["moves"],
"Frames": len(steps_heap),
"Sorted OK": steps_heap[-1]["array"] == sorted(data),
},
{
"Algorithm": "Shell Sort",
"Time (ms)": metrics_shell["seconds"] * 1000,
"Comparisons": metrics_shell["comparisons"],
"Moves": metrics_shell["moves"],
"Frames": len(steps_shell),
"Sorted OK": steps_shell[-1]["array"] == sorted(data),
},
{
"Algorithm": "Bucket Sort",
"Time (ms)": metrics_bucket["seconds"] * 1000,
"Comparisons": metrics_bucket["comparisons"],
"Moves": metrics_bucket["moves"],
"Frames": len(steps_bucket),
"Sorted OK": steps_bucket[-1]["array"] == sorted(data),
},
]
)
st.subheader("Summary Table")
st.dataframe(df.style.format({"Time (ms)": "{:.2f}"}), use_container_width=True)
csv = df.to_csv(index=False).encode("utf-8")
st.download_button(
"Download CSV", data=csv, file_name="sorting_summary.csv", mime="text/csv"
)