Python port & pushing visualization
This commit is contained in:
parent
f88d8c392a
commit
df2141c62d
61
cut_rod.py
Normal file
61
cut_rod.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def simple_cut_rod(p, n):
|
||||||
|
if n == 0:
|
||||||
|
return 0
|
||||||
|
q = float('-inf')
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
q = max(q, p[i] + simple_cut_rod(p, n - i))
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def _memoized_cut_rod_aux(p, n, r):
|
||||||
|
if r[n] >= 0:
|
||||||
|
return r[n]
|
||||||
|
if n == 0:
|
||||||
|
q = 0
|
||||||
|
else:
|
||||||
|
q = float('-inf')
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
q = max(q, p[i] + _memoized_cut_rod_aux(p, n - i, r))
|
||||||
|
r[n] = q
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def memoized_cut_rod(p, n):
|
||||||
|
r = [float('-inf')] * (n + 1)
|
||||||
|
return _memoized_cut_rod_aux(p, n, r)
|
||||||
|
|
||||||
|
|
||||||
|
def bottom_up_cut_rod(p, n):
|
||||||
|
r = [0] * (n + 1)
|
||||||
|
for j in range(1, n + 1):
|
||||||
|
q = float('-inf')
|
||||||
|
for i in range(1, j + 1):
|
||||||
|
q = max(q, p[i] + r[j - i])
|
||||||
|
r[j] = q
|
||||||
|
return r[n]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n = int(input("Enter number of Inches: "))
|
||||||
|
|
||||||
|
with open("Cut Rod Problem/Cut Rod Problem/Data.txt", encoding="utf-8-sig") as f:
|
||||||
|
price_data = f.read().split()
|
||||||
|
|
||||||
|
# 1-indexed: prices[0] unused, prices[i] = price for length i
|
||||||
|
prices = [0] + [int(x) for x in price_data]
|
||||||
|
|
||||||
|
start = time.perf_counter()
|
||||||
|
best_price = memoized_cut_rod(prices, n)
|
||||||
|
# best_price = bottom_up_cut_rod(prices, n)
|
||||||
|
# best_price = simple_cut_rod(prices, n)
|
||||||
|
elapsed = time.perf_counter() - start
|
||||||
|
|
||||||
|
print(f"Best Revenue is upto : {best_price}")
|
||||||
|
print(f"Time Elapsed : {elapsed:.9f}s")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
715
visualization.html
Normal file
715
visualization.html
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Cut Rod Problem — Live Visualizer</title>
|
||||||
|
<style>
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #0d1117;
|
||||||
|
--surf: #161b22;
|
||||||
|
--surf2: #21262d;
|
||||||
|
--border: #30363d;
|
||||||
|
--text: #e6edf3;
|
||||||
|
--dim: #7d8590;
|
||||||
|
--purple: #7c6aff;
|
||||||
|
--red: #ff6b6b;
|
||||||
|
--yellow: #ffd43b;
|
||||||
|
--green: #3fb950;
|
||||||
|
--blue: #58a6ff;
|
||||||
|
--orange: #f0883e;
|
||||||
|
--pink: #f778ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── HEADER ─────────────────────────────────── */
|
||||||
|
header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(120deg, var(--purple), var(--pink), var(--red));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
header p {
|
||||||
|
color: var(--dim);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── CONTROLS ────────────────────────────────── */
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--surf);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
max-width: 1120px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.ctrl-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.ctrl-group label { color: var(--dim); font-size: 0.82rem; white-space: nowrap; }
|
||||||
|
input[type=number] {
|
||||||
|
width: 64px;
|
||||||
|
background: var(--surf2);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
input[type=range] { width: 110px; accent-color: var(--purple); }
|
||||||
|
.speed-val { font-size: 0.8rem; color: var(--dim); min-width: 24px; }
|
||||||
|
.btn {
|
||||||
|
padding: 7px 18px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: opacity .15s, transform .1s;
|
||||||
|
}
|
||||||
|
.btn:active { transform: scale(.96); }
|
||||||
|
.btn-primary { background: var(--purple); color: #fff; }
|
||||||
|
.btn-primary:hover { opacity: .85; }
|
||||||
|
.btn-secondary { background: var(--surf2); color: var(--dim); border: 1px solid var(--border); }
|
||||||
|
.btn-secondary:hover { color: var(--text); border-color: #555; }
|
||||||
|
|
||||||
|
/* ── LAYOUT ──────────────────────────────────── */
|
||||||
|
.layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 320px;
|
||||||
|
gap: 16px;
|
||||||
|
max-width: 1120px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.layout { grid-template-columns: 1fr; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── PANEL ───────────────────────────────────── */
|
||||||
|
.panel {
|
||||||
|
background: var(--surf);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
}
|
||||||
|
.panel + .panel { margin-top: 14px; }
|
||||||
|
.panel-title {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .09em;
|
||||||
|
color: var(--dim);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ROD VISUAL ──────────────────────────────── */
|
||||||
|
#rod-desc {
|
||||||
|
font-size: 0.84rem;
|
||||||
|
color: var(--dim);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
.rod-bar {
|
||||||
|
display: flex;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transition: all .25s;
|
||||||
|
}
|
||||||
|
.rod-seg {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(0,0,0,.75);
|
||||||
|
transition: all .25s;
|
||||||
|
min-width: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.rod-seg .seg-len { font-size: .95rem; line-height: 1; }
|
||||||
|
.rod-seg .seg-price{ font-size: .65rem; opacity: .8; margin-top: 2px; }
|
||||||
|
.rod-cut {
|
||||||
|
width: 3px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 8px rgba(255,255,255,.7);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#rod-subtext {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--dim);
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── DP TABLE ────────────────────────────────── */
|
||||||
|
#dp-scroll { overflow-x: auto; }
|
||||||
|
.dp-tbl {
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: .82rem;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
.dp-tbl th, .dp-tbl td {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 7px 11px;
|
||||||
|
text-align: center;
|
||||||
|
transition: background .25s, color .2s, box-shadow .2s;
|
||||||
|
min-width: 38px;
|
||||||
|
}
|
||||||
|
.dp-tbl th { background: var(--surf2); color: var(--dim); font-size: .72rem; }
|
||||||
|
.dp-tbl td.empty { color: #3d444d; }
|
||||||
|
.dp-tbl td.done { background: #122620; color: var(--green); font-weight: 600; }
|
||||||
|
.dp-tbl td.active { background: #2b2400; color: var(--yellow); font-weight: 700; box-shadow: inset 0 0 0 2px var(--yellow); }
|
||||||
|
.dp-tbl td.ref { background: #0d1f36; color: var(--blue); font-weight: 600; }
|
||||||
|
.dp-tbl td.s-done { background: #1a1030; color: var(--purple); font-weight: 600; }
|
||||||
|
.dp-tbl td.s-active { background: #2b2400; color: var(--yellow); font-weight: 700; box-shadow: inset 0 0 0 2px var(--yellow); }
|
||||||
|
|
||||||
|
/* ── PROGRESS ────────────────────────────────── */
|
||||||
|
.prog-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
.prog-bar {
|
||||||
|
flex: 1;
|
||||||
|
height: 4px;
|
||||||
|
background: var(--surf2);
|
||||||
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.prog-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--purple), var(--pink));
|
||||||
|
transition: width .3s;
|
||||||
|
}
|
||||||
|
.prog-label { font-size: .72rem; color: var(--dim); white-space: nowrap; }
|
||||||
|
|
||||||
|
/* ── STEP INFO ───────────────────────────────── */
|
||||||
|
#step-msg {
|
||||||
|
font-size: .88rem;
|
||||||
|
line-height: 1.65;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
.formula-box {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 9px 13px;
|
||||||
|
background: var(--bg);
|
||||||
|
border-radius: 7px;
|
||||||
|
font-family: 'SFMono-Regular', 'Consolas', monospace;
|
||||||
|
font-size: .85rem;
|
||||||
|
border-left: 3px solid var(--purple);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── PSEUDOCODE ──────────────────────────────── */
|
||||||
|
.pseudo {
|
||||||
|
font-family: 'SFMono-Regular', 'Consolas', monospace;
|
||||||
|
font-size: .78rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
background: var(--bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
.pseudo .ln {
|
||||||
|
display: block;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background .2s, color .2s;
|
||||||
|
color: var(--dim);
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.pseudo .ln.hl {
|
||||||
|
background: #2b2400;
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
.pseudo .kw { color: var(--purple); }
|
||||||
|
.pseudo .num { color: var(--orange); }
|
||||||
|
|
||||||
|
/* ── PRICE TABLE ─────────────────────────────── */
|
||||||
|
#price-scroll { overflow-x: auto; }
|
||||||
|
.price-tbl {
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: .78rem;
|
||||||
|
}
|
||||||
|
.price-tbl th, .price-tbl td {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 5px 9px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.price-tbl th { background: var(--surf2); color: var(--dim); }
|
||||||
|
.price-tbl td { color: var(--green); font-weight: 600; }
|
||||||
|
.price-tbl td.hl-price { background: #122620; box-shadow: inset 0 0 0 2px var(--green); }
|
||||||
|
|
||||||
|
/* ── RESULT ──────────────────────────────────── */
|
||||||
|
#result-panel {
|
||||||
|
display: none;
|
||||||
|
background: #0a1f12;
|
||||||
|
border: 1px solid #2d6a4f;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
.result-rod {
|
||||||
|
display: flex;
|
||||||
|
height: 72px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
.result-seg {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(0,0,0,.8);
|
||||||
|
border-right: 2px solid rgba(0,0,0,.25);
|
||||||
|
min-width: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: flex .4s;
|
||||||
|
}
|
||||||
|
.result-seg:last-child { border-right: none; }
|
||||||
|
.result-seg .r-len { font-size: 1rem; }
|
||||||
|
.result-seg .r-price { font-size: .68rem; opacity: .85; margin-top: 2px; }
|
||||||
|
.stats-row { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||||
|
.stat {
|
||||||
|
background: var(--surf2);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 90px;
|
||||||
|
}
|
||||||
|
.stat .s-lbl { font-size: .7rem; color: var(--dim); margin-bottom: 4px; }
|
||||||
|
.stat .s-val { font-size: 1.3rem; font-weight: 700; color: var(--green); }
|
||||||
|
|
||||||
|
/* ── COLORS ──────────────────────────────────── */
|
||||||
|
.y { color: var(--yellow); }
|
||||||
|
.g { color: var(--green); }
|
||||||
|
.b { color: var(--blue); }
|
||||||
|
.r { color: var(--red); }
|
||||||
|
.p { color: var(--purple); }
|
||||||
|
.o { color: var(--orange); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>Cut Rod Problem</h1>
|
||||||
|
<p>Bottom-Up Dynamic Programming — step-by-step live visualization</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<div class="ctrl-group">
|
||||||
|
<label>Rod Length</label>
|
||||||
|
<input type="number" id="inp-n" value="10" min="1" max="25">
|
||||||
|
</div>
|
||||||
|
<div class="ctrl-group">
|
||||||
|
<label>Speed</label>
|
||||||
|
<input type="range" id="inp-speed" min="1" max="10" value="5">
|
||||||
|
<span class="speed-val" id="speed-lbl">5×</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" id="btn-play">▶ Play</button>
|
||||||
|
<button class="btn btn-secondary" id="btn-step">⏭ Step</button>
|
||||||
|
<button class="btn btn-secondary" id="btn-back">⏮ Back</button>
|
||||||
|
<button class="btn btn-secondary" id="btn-reset">↺ Reset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout">
|
||||||
|
|
||||||
|
<!-- LEFT COLUMN -->
|
||||||
|
<div>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Rod Visualization</div>
|
||||||
|
<div id="rod-desc"></div>
|
||||||
|
<div class="rod-bar" id="rod-bar"></div>
|
||||||
|
<div id="rod-subtext"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">DP Table</div>
|
||||||
|
<div id="dp-scroll"></div>
|
||||||
|
<div class="prog-row">
|
||||||
|
<div class="prog-bar"><div class="prog-fill" id="prog-fill" style="width:0%"></div></div>
|
||||||
|
<span class="prog-label" id="prog-lbl">0 / 0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="result-panel">
|
||||||
|
<div class="panel-title" style="color:var(--green)">Optimal Solution</div>
|
||||||
|
<div class="result-rod" id="result-rod"></div>
|
||||||
|
<div class="stats-row" id="stats-row"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RIGHT COLUMN -->
|
||||||
|
<div>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Current Step</div>
|
||||||
|
<div id="step-msg"><span style="color:var(--dim)">Press ▶ Play to begin…</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Algorithm</div>
|
||||||
|
<div class="pseudo" id="pseudo">
|
||||||
|
<span class="ln" data-ln="1"><span class="kw">def</span> bottom_up_cut_rod(p, n):</span>
|
||||||
|
<span class="ln" data-ln="2"> r = [<span class="num">0</span>] * (n + <span class="num">1</span>) <span style="color:#444"># r[0]=0</span></span>
|
||||||
|
<span class="ln" data-ln="3"> <span class="kw">for</span> j <span class="kw">in</span> range(<span class="num">1</span>, n+<span class="num">1</span>):</span>
|
||||||
|
<span class="ln" data-ln="4"> q = <span class="num">-∞</span></span>
|
||||||
|
<span class="ln" data-ln="5"> <span class="kw">for</span> i <span class="kw">in</span> range(<span class="num">1</span>, j+<span class="num">1</span>):</span>
|
||||||
|
<span class="ln" data-ln="6"> q = max(q, p[i] + r[j-i])</span>
|
||||||
|
<span class="ln" data-ln="7"> r[j] = q</span>
|
||||||
|
<span class="ln" data-ln="8"> <span class="kw">return</span> r[n]</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-title">Price Table (p[i])</div>
|
||||||
|
<div id="price-scroll"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── DATA ────────────────────────────────────────
|
||||||
|
const PRICE_DATA = [0,1,5,8,9,10,17,17,20,24,30,33,31,31,31,40,39,20,45,42,46,70,76,77,80,85,90,95,100,110,120,111,200,678];
|
||||||
|
|
||||||
|
const SEG_COLS = [
|
||||||
|
'#FF6B6B','#FFD43B','#69DB7C','#74C0FC','#FF922B',
|
||||||
|
'#DA77F2','#38D9A9','#F783AC','#A9E34B','#63E6BE',
|
||||||
|
'#FFA94D','#748FFC','#4DABF7','#F06595','#20C997',
|
||||||
|
'#E599F7','#FFC078','#6BCB77','#4D96FF','#CC5DE8',
|
||||||
|
'#FFD93D','#6BCBF7','#FF9F43','#A29BFE','#55EFC4',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ── STEP BUILDER ─────────────────────────────────
|
||||||
|
function buildSteps(prices, n) {
|
||||||
|
const steps = [];
|
||||||
|
const r = new Array(n + 1).fill(null);
|
||||||
|
const s = new Array(n + 1).fill(null);
|
||||||
|
r[0] = 0;
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'init', r:[...r], s:[...s], j:null, i:null, pseudo:2,
|
||||||
|
msg:`<span class="g">Initialize:</span> r[0] = 0 — a rod of length 0 earns nothing.`,
|
||||||
|
formula:`r[0] = 0`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
let q = -Infinity;
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'start_j', r:[...r], s:[...s], j, i:null, pseudo:3,
|
||||||
|
msg:`<span class="y">j = ${j}:</span> Computing best revenue for a rod of length <span class="y">${j}</span>.`,
|
||||||
|
formula:`r[${j}] = max(p[i] + r[${j}−i]) for i = 1…${j}`
|
||||||
|
});
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'init_q', r:[...r], s:[...s], j, i:null, pseudo:4,
|
||||||
|
msg:`Set q = −∞ — will track the best candidate for <span class="y">r[${j}]</span>.`,
|
||||||
|
formula:`q = −∞`
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 1; i <= j; i++) {
|
||||||
|
const rji = r[j - i] ?? 0;
|
||||||
|
const candidate = prices[i] + rji;
|
||||||
|
const isBetter = candidate > q;
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'try_cut', r:[...r], s:[...s], j, i, candidate, q, isBetter, pseudo:6,
|
||||||
|
msg:`Try cut at <span class="b">i = ${i}</span>: p[${i}] + r[${j-i}] = <span class="b">${prices[i]}</span> + <span class="b">${rji}</span> = <span class="${isBetter?'g':'r'}">${candidate}</span>${isBetter?' <span class="g">✓ new best</span>':''}`,
|
||||||
|
formula:`p[${i}] + r[${j-i}] = ${prices[i]} + ${rji} = ${candidate}`
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isBetter) {
|
||||||
|
q = candidate;
|
||||||
|
s[j] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r[j] = q;
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'set_r', r:[...r], s:[...s], j, i:s[j], pseudo:7,
|
||||||
|
msg:`<span class="g">r[${j}] = ${q}</span> — best first cut is at <span class="p">${s[j]}</span>, leaving remainder <span class="b">${j - s[j]}</span>.`,
|
||||||
|
formula:`r[${j}] = ${q} (s[${j}] = ${s[j]})`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconstruct optimal cuts
|
||||||
|
const cuts = [];
|
||||||
|
let rem = n;
|
||||||
|
while (rem > 0 && s[rem] != null) { cuts.push(s[rem]); rem -= s[rem]; }
|
||||||
|
|
||||||
|
steps.push({
|
||||||
|
type:'done', r:[...r], s:[...s], j:null, i:null, cuts, pseudo:8,
|
||||||
|
msg:`<span class="g">Done!</span> Maximum revenue for length ${n} is <span class="g">${r[n]}</span>.`,
|
||||||
|
formula:`Optimal cuts: [${cuts.join(', ')}]`
|
||||||
|
});
|
||||||
|
|
||||||
|
return steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── STATE ─────────────────────────────────────────
|
||||||
|
let steps = [], idx = 0, playing = false, timer = null, N = 10;
|
||||||
|
|
||||||
|
// ── RENDER HELPERS ────────────────────────────────
|
||||||
|
function getDelay() {
|
||||||
|
const s = parseInt(document.getElementById('inp-speed').value);
|
||||||
|
return Math.max(40, 1300 - s * 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderPriceTable(prices, n, hiI) {
|
||||||
|
const lim = Math.min(n, prices.length - 1);
|
||||||
|
let h = '<table class="price-tbl"><tr><th>i</th>';
|
||||||
|
for (let i = 1; i <= lim; i++) h += `<th>${i}</th>`;
|
||||||
|
h += '</tr><tr><th>p[i]</th>';
|
||||||
|
for (let i = 1; i <= lim; i++) {
|
||||||
|
const cls = i === hiI ? ' class="hl-price"' : '';
|
||||||
|
h += `<td${cls}>${prices[i]}</td>`;
|
||||||
|
}
|
||||||
|
h += '</tr></table>';
|
||||||
|
document.getElementById('price-scroll').innerHTML = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDpTable(r, s, n, activeJ, refJI) {
|
||||||
|
let h = '<table class="dp-tbl"><tr><th>j</th>';
|
||||||
|
for (let j = 0; j <= n; j++) h += `<th>${j}</th>`;
|
||||||
|
h += '</tr>';
|
||||||
|
|
||||||
|
// r row
|
||||||
|
h += '<tr><th>r[j]</th>';
|
||||||
|
for (let j = 0; j <= n; j++) {
|
||||||
|
let cls = 'empty'; let val = '—';
|
||||||
|
if (r[j] !== null) { cls = 'done'; val = r[j]; }
|
||||||
|
if (j === activeJ) cls = 'active';
|
||||||
|
if (j === refJI && j !== activeJ && r[j] !== null) cls = 'ref';
|
||||||
|
h += `<td class="${cls}">${val}</td>`;
|
||||||
|
}
|
||||||
|
h += '</tr>';
|
||||||
|
|
||||||
|
// s row
|
||||||
|
h += '<tr><th>s[j]</th>';
|
||||||
|
for (let j = 0; j <= n; j++) {
|
||||||
|
let cls = 'empty'; let val = '—';
|
||||||
|
if (s[j] !== null) { cls = 's-done'; val = s[j]; }
|
||||||
|
if (j === activeJ) cls = 's-active';
|
||||||
|
h += `<td class="${cls}">${val}</td>`;
|
||||||
|
}
|
||||||
|
h += '</tr></table>';
|
||||||
|
|
||||||
|
document.getElementById('dp-scroll').innerHTML = h;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRod(j, i, r, prices) {
|
||||||
|
const bar = document.getElementById('rod-bar');
|
||||||
|
const desc = document.getElementById('rod-desc');
|
||||||
|
const sub = document.getElementById('rod-subtext');
|
||||||
|
bar.innerHTML = '';
|
||||||
|
|
||||||
|
if (!j) { desc.innerHTML = ''; sub.innerHTML = ''; return; }
|
||||||
|
|
||||||
|
desc.innerHTML = `Rod of length <span class="y">${j}</span>`;
|
||||||
|
|
||||||
|
if (i === null) {
|
||||||
|
// Whole rod, no cut shown
|
||||||
|
const seg = document.createElement('div');
|
||||||
|
seg.className = 'rod-seg';
|
||||||
|
seg.style.cssText = `background:${SEG_COLS[j % SEG_COLS.length]};flex:${j}`;
|
||||||
|
seg.innerHTML = `<span class="seg-len">${j}</span>`;
|
||||||
|
bar.appendChild(seg);
|
||||||
|
sub.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left piece
|
||||||
|
const left = document.createElement('div');
|
||||||
|
left.className = 'rod-seg';
|
||||||
|
left.style.cssText = `background:var(--red);flex:${i}`;
|
||||||
|
left.innerHTML = `<span class="seg-len">${i}</span><span class="seg-price">$${prices[i]}</span>`;
|
||||||
|
bar.appendChild(left);
|
||||||
|
|
||||||
|
const remainder = j - i;
|
||||||
|
if (remainder > 0) {
|
||||||
|
const cut = document.createElement('div');
|
||||||
|
cut.className = 'rod-cut';
|
||||||
|
bar.appendChild(cut);
|
||||||
|
|
||||||
|
const right = document.createElement('div');
|
||||||
|
right.className = 'rod-seg';
|
||||||
|
right.style.cssText = `background:var(--blue);flex:${remainder}`;
|
||||||
|
const rVal = r[remainder] ?? 0;
|
||||||
|
right.innerHTML = `<span class="seg-len">${remainder}</span><span class="seg-price">r=${rVal}</span>`;
|
||||||
|
bar.appendChild(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rji = r[j - i] ?? 0;
|
||||||
|
sub.innerHTML =
|
||||||
|
`<span style="color:var(--red)">■</span> Cut piece: length <b>${i}</b>, price = <b>$${prices[i]}</b>` +
|
||||||
|
` <span style="color:var(--blue)">■</span> Remainder: length <b>${j-i}</b>, best revenue = <b>${rji}</b>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResult(cuts, r, prices, n) {
|
||||||
|
const panel = document.getElementById('result-panel');
|
||||||
|
const rod = document.getElementById('result-rod');
|
||||||
|
const stats = document.getElementById('stats-row');
|
||||||
|
panel.style.display = 'block';
|
||||||
|
rod.innerHTML = '';
|
||||||
|
|
||||||
|
cuts.forEach((len, k) => {
|
||||||
|
const seg = document.createElement('div');
|
||||||
|
seg.className = 'result-seg';
|
||||||
|
seg.style.cssText = `background:${SEG_COLS[k % SEG_COLS.length]};flex:${len}`;
|
||||||
|
seg.innerHTML = `<span class="r-len">${len}"</span><span class="r-price">$${prices[len]}</span>`;
|
||||||
|
rod.appendChild(seg);
|
||||||
|
});
|
||||||
|
|
||||||
|
const revenue = r[n];
|
||||||
|
const nCuts = cuts.length - 1;
|
||||||
|
stats.innerHTML = `
|
||||||
|
<div class="stat"><div class="s-lbl">Best Revenue</div><div class="s-val">$${revenue}</div></div>
|
||||||
|
<div class="stat"><div class="s-lbl">Cuts Made</div><div class="s-val">${nCuts}</div></div>
|
||||||
|
<div class="stat"><div class="s-lbl">Pieces</div><div class="s-val" style="font-size:.95rem">${cuts.join(' + ')}</div></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightPseudo(ln) {
|
||||||
|
document.querySelectorAll('#pseudo .ln').forEach(el => {
|
||||||
|
el.classList.toggle('hl', parseInt(el.dataset.ln) === ln);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── APPLY STEP ────────────────────────────────────
|
||||||
|
function applyStep(step) {
|
||||||
|
const { type, r, s, j, i, msg, formula, pseudo, cuts } = step;
|
||||||
|
|
||||||
|
// Step info
|
||||||
|
document.getElementById('step-msg').innerHTML =
|
||||||
|
msg + (formula ? `<div class="formula-box">${formula}</div>` : '');
|
||||||
|
|
||||||
|
// Pseudocode highlight
|
||||||
|
highlightPseudo(pseudo);
|
||||||
|
|
||||||
|
// Determine referenced cell in DP table
|
||||||
|
const refCell = (type === 'try_cut') ? j - i : null;
|
||||||
|
renderDpTable(r, s, N, (type === 'done' || type === 'init') ? null : j, refCell);
|
||||||
|
|
||||||
|
// Rod
|
||||||
|
if (type === 'init' || type === 'done') {
|
||||||
|
renderRod(null, null, r, PRICE_DATA);
|
||||||
|
} else if (type === 'start_j' || type === 'set_r' || type === 'init_q') {
|
||||||
|
renderRod(j, null, r, PRICE_DATA);
|
||||||
|
} else if (type === 'try_cut') {
|
||||||
|
renderRod(j, i, r, PRICE_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Price table highlight
|
||||||
|
renderPriceTable(PRICE_DATA, N, type === 'try_cut' ? i : null);
|
||||||
|
|
||||||
|
// Result
|
||||||
|
if (type === 'done') {
|
||||||
|
renderResult(cuts, r, PRICE_DATA, N);
|
||||||
|
stopPlay();
|
||||||
|
} else {
|
||||||
|
document.getElementById('result-panel').style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
const pct = steps.length > 1 ? (idx / (steps.length - 1)) * 100 : 0;
|
||||||
|
document.getElementById('prog-fill').style.width = pct + '%';
|
||||||
|
document.getElementById('prog-lbl').textContent = `${idx + 1} / ${steps.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── PLAYBACK ──────────────────────────────────────
|
||||||
|
function stopPlay() {
|
||||||
|
playing = false;
|
||||||
|
clearTimeout(timer);
|
||||||
|
document.getElementById('btn-play').textContent = '▶ Play';
|
||||||
|
}
|
||||||
|
|
||||||
|
function advance() {
|
||||||
|
if (idx < steps.length - 1) {
|
||||||
|
idx++;
|
||||||
|
applyStep(steps[idx]);
|
||||||
|
if (playing && idx < steps.length - 1) {
|
||||||
|
timer = setTimeout(advance, getDelay());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
N = Math.max(1, Math.min(25, parseInt(document.getElementById('inp-n').value) || 10));
|
||||||
|
steps = buildSteps(PRICE_DATA, N);
|
||||||
|
idx = 0;
|
||||||
|
stopPlay();
|
||||||
|
document.getElementById('result-panel').style.display = 'none';
|
||||||
|
renderPriceTable(PRICE_DATA, N, null);
|
||||||
|
applyStep(steps[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── EVENTS ────────────────────────────────────────
|
||||||
|
document.getElementById('btn-play').addEventListener('click', () => {
|
||||||
|
if (playing) {
|
||||||
|
stopPlay();
|
||||||
|
} else {
|
||||||
|
if (idx >= steps.length - 1) init();
|
||||||
|
playing = true;
|
||||||
|
document.getElementById('btn-play').textContent = '⏸ Pause';
|
||||||
|
timer = setTimeout(advance, getDelay());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-step').addEventListener('click', () => {
|
||||||
|
stopPlay();
|
||||||
|
if (idx < steps.length - 1) { idx++; applyStep(steps[idx]); }
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-back').addEventListener('click', () => {
|
||||||
|
stopPlay();
|
||||||
|
if (idx > 0) { idx--; applyStep(steps[idx]); }
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-reset').addEventListener('click', () => {
|
||||||
|
stopPlay();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('inp-n').addEventListener('change', () => {
|
||||||
|
stopPlay();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('inp-speed').addEventListener('input', e => {
|
||||||
|
document.getElementById('speed-lbl').textContent = e.target.value + '×';
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── BOOT ─────────────────────────────────────────
|
||||||
|
init();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user