Try it. You must ship 4 units from source to sink. Three routes are available, each with a capacity and a cost per unit. Use the +/− buttons to split your shipment across them so the total flow is 4 — and the total cost is as low as possible.
<p class="hint">Ship <b>4 units</b> from source to sink. Split them across three routes (each with a <b>capacity</b> and a <b>cost per unit</b>) with the +/− buttons. Reach 4 units at the lowest total cost.</p>
<div id="routes" class="routes"></div>
<div class="meter">
<div>Shipped: <b id="ship">0</b> / 4</div>
<div>Total cost: <b id="cost" class="cost">0</b></div>
<div id="status" class="status"></div>
</div>
<div class="btns">
<button id="opt" type="button">Optimize</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.routes { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 1rem; }
.route { border: 1px solid #e2e6eb; border-radius: 10px; padding: .6rem .8rem; display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.rname { font: 800 14px system-ui; color: #1d3557; min-width: 70px; }
.rinfo { font: 600 12.5px system-ui; color: #777; flex: 1; min-width: 120px; }
.ctrl { display: flex; align-items: center; gap: .4rem; }
.ctrl button { width: 30px; height: 30px; padding: 0; font: 800 16px system-ui; }
.fval { font: 800 18px ui-monospace, monospace; color: #2a9d8f; min-width: 1.6rem; text-align: center; }
.bar { height: 8px; background: #eef1f4; border-radius: 4px; overflow: hidden; flex-basis: 100%; margin-top: .3rem; }
.bar .fill { height: 100%; background: #2a9d8f; }
.meter { display: flex; gap: 1.3rem; align-items: center; margin-bottom: .6rem; font-size: 1rem; flex-wrap: wrap; }
.meter b { color: #1d3557; font-family: ui-monospace, monospace; }
.cost { color: #457b9d !important; }
.status { font-weight: 800; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .5rem 1rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Minimum-cost flow (here a simple parallel-route version, polynomial-time).
// Ship the demand across routes with capacities and unit costs at least total
// cost. Cheapest routes fill first but run out — Optimize finds the cheapest plan.
const DEMAND = 4;
const ROUTES = [
{ name: 'Route A', cap: 2, cost: 1 },
{ name: 'Route B', cap: 2, cost: 3 },
{ name: 'Route C', cap: 3, cost: 4 }
];
let flow = ROUTES.map(() => 0);
function totalShip() { return flow.reduce((a, b) => a + b, 0); }
function totalCost() { return flow.reduce((a, f, i) => a + f * ROUTES[i].cost, 0); }
// optimum: greedily fill by cheapest cost (optimal for parallel routes)
function optimum() {
const order = ROUTES.map((r, i) => i).sort((a, b) => ROUTES[a].cost - ROUTES[b].cost);
const f = ROUTES.map(() => 0); let need = DEMAND;
for (const i of order) { const take = Math.min(ROUTES[i].cap, need); f[i] = take; need -= take; }
return f;
}
const OPT = optimum(); const OPTCOST = OPT.reduce((a, f, i) => a + f * ROUTES[i].cost, 0);
function render() {
document.getElementById('routes').innerHTML = ROUTES.map((r, i) =>
fmt('<div class="route"><span class="rname">%name%</span><span class="rinfo">capacity %cap% · $%cost%/unit</span><span class="ctrl"><button data-d="-1" data-i="%i%">−</button><span class="fval">%v%</span><button data-d="1" data-i="%i%">+</button></span><div class="bar"><div class="fill" style="width:%v2%%"></div></div></div>', { name: r.name, cap: r.cap, cost: r.cost, i: i, v: flow[i], v2: flow[i] / r.cap * 100 })).join('');
document.querySelectorAll('.ctrl button').forEach(b => b.addEventListener('click', () => {
const i = +b.dataset.i, d = +b.dataset.d;
const nf = flow[i] + d;
if (nf < 0 || nf > ROUTES[i].cap) return;
if (d > 0 && totalShip() >= DEMAND) return;
flow[i] = nf; render();
}));
document.getElementById('ship').textContent = totalShip();
document.getElementById('cost').textContent = '
Fill the cheap route first, but it caps out — so you spill into pricier ones. Hit Optimize for the cheapest feasible plan. It's the everyday tension of logistics: the lowest-cost edges are exactly the ones that run out of room first.
+ totalCost();
const st = document.getElementById('status');
if (totalShip() < DEMAND) { st.style.color = '#777'; st.textContent = fmt('Ship %v% more unit(s).', { v: DEMAND - totalShip() }); }
else if (totalCost() <= OPTCOST) { st.style.color = '#0a7d33'; st.textContent = fmt('✓ Cheapest plan! ($%OPTCOST%)', { OPTCOST: OPTCOST }); }
else { st.style.color = '#b9770e'; st.textContent = fmt('Done, but $ $%v% over the optimum ($%OPTCOST%).', { v: totalCost() - OPTCOST, OPTCOST: OPTCOST }); }
}
document.getElementById('opt').addEventListener('click', () => { flow = OPT.slice(); render(); });
document.getElementById('reset').addEventListener('click', () => { flow = ROUTES.map(() => 0); render(); });
render();
<p class="hint">Ship <b>4 units</b> from source to sink. Split them across three routes (each with a <b>capacity</b> and a <b>cost per unit</b>) with the +/− buttons. Reach 4 units at the lowest total cost.</p>
<div id="routes" class="routes"></div>
<div class="meter">
<div>Shipped: <b id="ship">0</b> / 4</div>
<div>Total cost: <b id="cost" class="cost">0</b></div>
<div id="status" class="status"></div>
</div>
<div class="btns">
<button id="opt" type="button">Optimize</button>
<button id="reset" type="button" class="ghost">Clear</button>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .9rem; color: #444; margin: 0 0 .8rem; line-height: 1.45; }
.routes { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 1rem; }
.route { border: 1px solid #e2e6eb; border-radius: 10px; padding: .6rem .8rem; display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.rname { font: 800 14px system-ui; color: #1d3557; min-width: 70px; }
.rinfo { font: 600 12.5px system-ui; color: #777; flex: 1; min-width: 120px; }
.ctrl { display: flex; align-items: center; gap: .4rem; }
.ctrl button { width: 30px; height: 30px; padding: 0; font: 800 16px system-ui; }
.fval { font: 800 18px ui-monospace, monospace; color: #2a9d8f; min-width: 1.6rem; text-align: center; }
.bar { height: 8px; background: #eef1f4; border-radius: 4px; overflow: hidden; flex-basis: 100%; margin-top: .3rem; }
.bar .fill { height: 100%; background: #2a9d8f; }
.meter { display: flex; gap: 1.3rem; align-items: center; margin-bottom: .6rem; font-size: 1rem; flex-wrap: wrap; }
.meter b { color: #1d3557; font-family: ui-monospace, monospace; }
.cost { color: #457b9d !important; }
.status { font-weight: 800; }
.btns { display: flex; gap: .5rem; }
button { font: 600 14px system-ui, sans-serif; padding: .5rem 1rem; border: 1px solid #457b9d; background: #457b9d; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #457b9d; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Minimum-cost flow (here a simple parallel-route version, polynomial-time).
// Ship the demand across routes with capacities and unit costs at least total
// cost. Cheapest routes fill first but run out — Optimize finds the cheapest plan.
const DEMAND = 4;
const ROUTES = [
{ name: 'Route A', cap: 2, cost: 1 },
{ name: 'Route B', cap: 2, cost: 3 },
{ name: 'Route C', cap: 3, cost: 4 }
];
let flow = ROUTES.map(() => 0);
function totalShip() { return flow.reduce((a, b) => a + b, 0); }
function totalCost() { return flow.reduce((a, f, i) => a + f * ROUTES[i].cost, 0); }
// optimum: greedily fill by cheapest cost (optimal for parallel routes)
function optimum() {
const order = ROUTES.map((r, i) => i).sort((a, b) => ROUTES[a].cost - ROUTES[b].cost);
const f = ROUTES.map(() => 0); let need = DEMAND;
for (const i of order) { const take = Math.min(ROUTES[i].cap, need); f[i] = take; need -= take; }
return f;
}
const OPT = optimum(); const OPTCOST = OPT.reduce((a, f, i) => a + f * ROUTES[i].cost, 0);
function render() {
document.getElementById('routes').innerHTML = ROUTES.map((r, i) =>
fmt('<div class="route"><span class="rname">%name%</span><span class="rinfo">capacity %cap% · $%cost%/unit</span><span class="ctrl"><button data-d="-1" data-i="%i%">−</button><span class="fval">%v%</span><button data-d="1" data-i="%i%">+</button></span><div class="bar"><div class="fill" style="width:%v2%%"></div></div></div>', { name: r.name, cap: r.cap, cost: r.cost, i: i, v: flow[i], v2: flow[i] / r.cap * 100 })).join('');
document.querySelectorAll('.ctrl button').forEach(b => b.addEventListener('click', () => {
const i = +b.dataset.i, d = +b.dataset.d;
const nf = flow[i] + d;
if (nf < 0 || nf > ROUTES[i].cap) return;
if (d > 0 && totalShip() >= DEMAND) return;
flow[i] = nf; render();
}));
document.getElementById('ship').textContent = totalShip();
document.getElementById('cost').textContent = '
Fill the cheap route first, but it caps out — so you spill into pricier ones. Hit Optimize for the cheapest feasible plan. It's the everyday tension of logistics: the lowest-cost edges are exactly the ones that run out of room first.
+ totalCost();
const st = document.getElementById('status');
if (totalShip() < DEMAND) { st.style.color = '#777'; st.textContent = fmt('Ship %v% more unit(s).', { v: DEMAND - totalShip() }); }
else if (totalCost() <= OPTCOST) { st.style.color = '#0a7d33'; st.textContent = fmt('✓ Cheapest plan! ($%OPTCOST%)', { OPTCOST: OPTCOST }); }
else { st.style.color = '#b9770e'; st.textContent = fmt('Done, but $ $%v% over the optimum ($%OPTCOST%).', { v: totalCost() - OPTCOST, OPTCOST: OPTCOST }); }
}
document.getElementById('opt').addEventListener('click', () => { flow = OPT.slice(); render(); });
document.getElementById('reset').addEventListener('click', () => { flow = ROUTES.map(() => 0); render(); });
render();
Fill the cheap route first, but it caps out — so you spill into pricier ones. Hit Optimize for the cheapest feasible plan. It's the everyday tension of logistics: the lowest-cost edges are exactly the ones that run out of room first.
Comments
Loading comments...