DEV Community

Cover image for Adaptive Sorting Visualizer
DHEERAJ KUMAR
DHEERAJ KUMAR

Posted on

Adaptive Sorting Visualizer

from flask import Flask, render_template_string, request, jsonify
import csv, io, time

app = Flask(__name__)

# ================= SORTING LOGIC =====================

def bubble_sort(a):
    arr = a[:]; ops = []; comps = swaps = 0
    n = len(arr)
    for i in range(n-1):
        for j in range(n-i-1):
            ops.append({"type":"compare","i":j,"j":j+1}); comps += 1
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                ops.append({"type":"swap","i":j,"j":j+1}); swaps += 1
    return arr, ops, comps, swaps

def insertion_sort(a):
    arr = a[:]; ops = []; comps = swaps = 0
    for i in range(1,len(arr)):
        key = arr[i]; j = i-1
        while j >= 0:
            ops.append({"type":"compare","i":j,"j":i}); comps += 1
            if arr[j] > key:
                arr[j+1] = arr[j]
                ops.append({"type":"overwrite","i":j+1,"value":arr[j]})
                swaps += 1; j -= 1
            else: break
        arr[j+1] = key
        ops.append({"type":"overwrite","i":j+1,"value":key})
    return arr, ops, comps, swaps

def quick_sort(a):
    arr=a[:]; ops=[]; comps=swaps=0
    def partition(l,h):
        nonlocal comps,swaps
        pivot=arr[h]; i=l-1
        for j in range(l,h):
            ops.append({"type":"compare","i":j,"j":h}); comps+=1
            if arr[j]<pivot:
                i+=1; arr[i],arr[j]=arr[j],arr[i]
                ops.append({"type":"swap","i":i,"j":j}); swaps+=1
        arr[i+1],arr[h]=arr[h],arr[i+1]
        ops.append({"type":"swap","i":i+1,"j":h}); swaps+=1
        return i+1
    def q(l,h):
        if l<h:
            p=partition(l,h); q(l,p-1); q(p+1,h)
    if len(arr)>0:q(0,len(arr)-1)
    return arr,ops,comps,swaps

def merge_sort(a):
    arr=a[:]; ops=[]; comps=swaps=0
    def merge(l,m,r):
        nonlocal comps,swaps
        L,R=arr[l:m+1],arr[m+1:r+1]; i=j=0; k=l
        while i<len(L) and j<len(R):
            ops.append({"type":"compare","i":l+i,"j":m+1+j}); comps+=1
            if L[i]<=R[j]:
                arr[k]=L[i]; ops.append({"type":"overwrite","i":k,"value":L[i]}); i+=1
            else:
                arr[k]=R[j]; ops.append({"type":"overwrite","i":k,"value":R[j]}); j+=1
            swaps+=1; k+=1
        while i<len(L):
            arr[k]=L[i]; ops.append({"type":"overwrite","i":k,"value":L[i]})
            i+=1; k+=1; swaps+=1
        while j<len(R):
            arr[k]=R[j]; ops.append({"type":"overwrite","i":k,"value":R[j]})
            j+=1; k+=1; swaps+=1
    def m(l,r):
        if l<r:
            mid=(l+r)//2; m(l,mid); m(mid+1,r); merge(l,mid,r)
    if len(arr)>0:m(0,len(arr)-1)
    return arr,ops,comps,swaps

THEORY = {
    "bubble":{"time":"O(n²)","space":"O(1)","stable":"Yes"},
    "insertion":{"time":"O(n)","space":"O(1)","stable":"Yes"},
    "quick":{"time":"O(n log n)","space":"O(log n)","stable":"No"},
    "merge":{"time":"O(n log n)","space":"O(n)","stable":"Yes"}
}

# ================= FLASK ROUTES =====================
@app.route("/")
def index():
    return render_template_string("""
<!DOCTYPE html><html><head><meta charset="utf-8">
<title>Adaptive Sorting Visualizer (CSV Upload)</title>
<style>
body{background:#0f172a;color:#f8fafc;text-align:center;font-family:Arial}
#bars{display:flex;align-items:flex-end;justify-content:center;height:400px;margin:20px;gap:4px}
.bar-container{display:flex;flex-direction:column;align-items:center;justify-content:flex-end}
.bar{background:#38bdf8;width:12px;transition:all .1s}
.bar-value{color:#f8fafc;font-size:10px;margin-top:2px}
.bar.comp{background:#facc15}
.bar.swap{background:#ef4444}
input,button{padding:8px;margin:5px;border:none;border-radius:6px}
button{background:#38bdf8;font-weight:bold;cursor:pointer}
.stats{margin-top:10px;font-size:14px}
#addForm{margin-top:20px}
</style></head><body>
<h2>🧩 Adaptive Sorting Visualizer (Upload CSV)</h2>
<form id="uploadForm" enctype="multipart/form-data">
  <input type="file" name="file" accept=".csv" required>
  <label><input type="checkbox" id="stable"> Prefer Stable Sort </label>
  <button type="submit">Upload & Analyze</button>
</form>
<div id="bars"></div>
<div class="stats" id="stats"></div>

<div id="addForm" style="display:none;">
  <h3>➕ Add New Data Point</h3>
  <input type="number" id="newVal" placeholder="Enter number">
  <button onclick="addData()">Add & Re-sort</button>
</div>

<script>
let arr=[],ops=[],index=0,maxVal=1,lastAlgo="insertion";

document.getElementById("uploadForm").addEventListener("submit",async e=>{
 e.preventDefault();
 const f=new FormData(e.target);
 f.set("stable",document.getElementById("stable").checked?'true':'false');
 const res=await fetch("/upload",{method:"POST",body:f});
 const data=await res.json();
 arr=data.original_array;ops=data.operations;maxVal=Math.max(...arr);
 lastAlgo=data.algorithm;
 draw();index=0;animate();
 showStats(data);
 document.getElementById("addForm").style.display='block';
});

function draw(){
 const bars=document.getElementById("bars");bars.innerHTML='';
 arr.forEach(v=>{
   const cont=document.createElement('div');
   cont.className='bar-container';
   const b=document.createElement('div');
   b.className='bar';
   b.style.height=(v/maxVal*380)+'px';
   b.style.width=(600/arr.length)+'px';
   const val=document.createElement('div');
   val.className='bar-value';
   val.textContent=v;
   cont.appendChild(b);
   cont.appendChild(val);
   bars.appendChild(cont);
 });
}

function animate(){
 if(index>=ops.length)return;
 const o=ops[index++];const bars=document.querySelectorAll('.bar');
 const values=document.querySelectorAll('.bar-value');
 bars.forEach(x=>x.classList.remove('comp','swap'));
 if(o.type==='compare'){bars[o.i].classList.add('comp');bars[o.j].classList.add('comp');}
 if(o.type==='swap'){
   let h=bars[o.i].style.height;bars[o.i].style.height=bars[o.j].style.height;bars[o.j].style.height=h;
   let tmpVal=values[o.i].textContent;values[o.i].textContent=values[o.j].textContent;values[o.j].textContent=tmpVal;
   bars[o.i].classList.add('swap');bars[o.j].classList.add('swap');
 }
 if(o.type==='overwrite'){
   bars[o.i].style.height=(o.value/maxVal*380)+'px';
   values[o.i].textContent=o.value;
   bars[o.i].classList.add('swap');
 }
 setTimeout(animate,20);
}

function showStats(d){
 document.getElementById("stats").innerHTML=
 `Algorithm: <b>${d.algorithm.toUpperCase()}</b> | Size: ${d.size} | Comparisons: ${d.comparisons} | Swaps/Writes: ${d.swaps_writes} | Time: ${d.time_taken.toFixed(4)}s<br>
 Stable: ${d.theoretical.stable} | Time Complexity: ${d.theoretical.time} | Space: ${d.theoretical.space}`;
}

async function addData(){
 const val=parseFloat(document.getElementById("newVal").value);
 if(isNaN(val))return alert("Enter valid number!");
 arr.push(val);
 const res=await fetch("/add_data",{method:"POST",headers:{'Content-Type':'application/json'},body:JSON.stringify({data:arr})});
 const data=await res.json();
 arr=data.original_array;ops=data.operations;maxVal=Math.max(...arr);
 draw();index=0;animate();showStats(data);
}
</script></body></html>
""")

# ================= API ROUTES =====================

@app.route("/upload", methods=["POST"])
def upload():
    file = request.files.get("file")
    prefer_stable = request.form.get("stable") == "true"
    if not file: return jsonify({"error": "No file uploaded"}), 400
    stream = io.StringIO(file.stream.read().decode("utf-8"))
    reader = csv.reader(stream)
    arr=[]
    for row in reader:
        for val in row:
            try: arr.append(float(val))
            except: pass
    n=len(arr)
    if n==0: return jsonify({"error": "No valid numbers"}),400
    if n <= 30:
        algo = "bubble"
    elif prefer_stable:
        algo = "merge"
    else:
        algo = "quick"
    start=time.time()
    if algo=="bubble": sorted_arr,ops,comps,swaps=bubble_sort(arr)
    elif algo=="merge": sorted_arr,ops,comps,swaps=merge_sort(arr)
    elif algo=="insertion": sorted_arr,ops,comps,swaps=insertion_sort(arr)
    else: sorted_arr,ops,comps,swaps=quick_sort(arr)
    elapsed=time.time()-start
    return jsonify({
        "algorithm":algo,
        "size":n,
        "original_array":arr,
        "operations":ops,
        "comparisons":comps,
        "swaps_writes":swaps,
        "time_taken":elapsed,
        "theoretical":THEORY[algo]
    })

@app.route("/add_data", methods=["POST"])
def add_data():
    data = request.get_json().get("data", [])
    if not data: return jsonify({"error":"No data"}),400
    start=time.time()
    sorted_arr,ops,comps,swaps=insertion_sort(data)
    elapsed=time.time()-start
    return jsonify({
        "algorithm":"insertion",
        "size":len(sorted_arr),
        "original_array":sorted_arr,
        "operations":ops,
        "comparisons":comps,
        "swaps_writes":swaps,
        "time_taken":elapsed,
        "theoretical":THEORY["insertion"]
    })

if __name__=="__main__":
    app.run(debug=True)

Enter fullscreen mode Exit fullscreen mode

Top comments (0)