Try it. Six items need covering, and you have several sets to choose from. Click a set to add it; the items it covers light up green. Cover all six using as few sets as you can.
<p class="hint">Six items (1–6) must all be covered. Each set below covers some of them. Click sets to add them; covered items turn green. Cover <b>all six</b> using as <b>few sets</b> as possible.</p>
<div class="universe" id="universe"></div>
<div class="sets" id="sets"></div>
<p id="status" class="status"></p>
<div class="bar-btns">
<button id="greedy" type="button">Greedy</button>
<button id="opt" type="button">Optimal</button>
<button id="reset" type="button" class="ghost">Reset</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 .6rem; line-height: 1.45; }
.universe { display: flex; gap: .4rem; margin-bottom: .7rem; }
.item { width: 40px; height: 40px; border-radius: 8px; background: #e9ecef; border: 2px solid #ccc; display: flex; align-items: center; justify-content: center; font: 700 16px ui-monospace, monospace; color: #888; transition: all .12s; }
.item.cov { background: #2a9d8f; border-color: #2a9d8f; color: #fff; }
.sets { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: .5rem; }
.set { border: 2px solid #ddd; border-radius: 10px; padding: .5rem .6rem; cursor: pointer; background: #fff; transition: all .12s; user-select: none; }
.set.on { border-color: #457b9d; background: #eaf1f6; box-shadow: 0 2px 8px rgba(69,123,157,.2); }
.set .nm { font-weight: 700; font-size: .95rem; }
.set .el { font: 13px ui-monospace, monospace; color: #555; margin-top: .2rem; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.3em; margin: .5rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c0392b; }
.bar-btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; 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] ?? ''));
// Set cover: fewest sets whose union is the whole universe. NP-hard; the greedy
// "cover the most uncovered" rule is within ln n of optimal (and that's the best possible).
const UNIVERSE = [1, 2, 3, 4, 5, 6];
const SETS = [
{ id: 'A', el: [1, 2, 3] }, { id: 'B', el: [4, 5, 6] }, { id: 'C', el: [2, 3, 4, 5] },
{ id: 'D', el: [1, 2] }, { id: 'E', el: [5, 6] },
];
let chosen = new Set();
const coveredItems = () => { const s = new Set(); SETS.forEach(S => { if (chosen.has(S.id)) S.el.forEach(e => s.add(e)); }); return s; };
function render() {
const cov = coveredItems();
const uni = document.getElementById('universe'); uni.innerHTML = '';
UNIVERSE.forEach(n => { const d = document.createElement('div'); d.className = 'item' + (cov.has(n) ? ' cov' : ''); d.textContent = n; uni.appendChild(d); });
const box = document.getElementById('sets'); box.innerHTML = '';
SETS.forEach(S => {
const el = document.createElement('div');
el.className = 'set' + (chosen.has(S.id) ? ' on' : '');
el.innerHTML = fmt('<div class="nm">Set %id%</div><div class="el">{ %el% }</div>', { id: S.id, el: S.el.join(', ') });
el.addEventListener('click', () => { chosen.has(S.id) ? chosen.delete(S.id) : chosen.add(S.id); render(); });
box.appendChild(el);
});
const st = document.getElementById('status');
const c = cov.size, k = chosen.size;
if (c < UNIVERSE.length) { st.className = 'status bad'; st.textContent = fmt('%c% of %length% items covered with %k% set(s) — keep going.', { c: c, length: UNIVERSE.length, k: k }); }
else if (k <= 2) { st.className = 'status ok'; st.textContent = fmt('All covered with %k% sets — that’s the minimum! 🎉', { k: k }); }
else { st.className = 'status ok'; st.textContent = fmt('All covered with %k% sets. The minimum is 2 — can you trim it?', { k: k }); }
}
function greedy() {
chosen = new Set(); const cov = new Set();
while (cov.size < UNIVERSE.length) {
let best = null, gain = -1;
for (const S of SETS) { if (chosen.has(S.id)) continue; const g = S.el.filter(e => !cov.has(e)).length; if (g > gain) { gain = g; best = S; } }
if (!best || gain === 0) break;
chosen.add(best.id); best.el.forEach(e => cov.add(e));
}
render();
console.log(fmt('Greedy used %size% sets: %v% (optimal 2).', { size: chosen.size, v: [...chosen].join(', ') }));
}
function optimal() {
let best = null;
for (let m = 1; m < (1 << SETS.length); m++) {
const pick = SETS.filter((_, i) => m & (1 << i));
const u = new Set(); pick.forEach(S => S.el.forEach(e => u.add(e)));
if (u.size === UNIVERSE.length && (best === null || pick.length < best.length)) best = pick;
}
chosen = new Set(best.map(S => S.id)); render();
}
document.getElementById('greedy').addEventListener('click', greedy);
document.getElementById('opt').addEventListener('click', optimal);
document.getElementById('reset').addEventListener('click', () => { chosen = new Set(); render(); });
render();
<p class="hint">Six items (1–6) must all be covered. Each set below covers some of them. Click sets to add them; covered items turn green. Cover <b>all six</b> using as <b>few sets</b> as possible.</p>
<div class="universe" id="universe"></div>
<div class="sets" id="sets"></div>
<p id="status" class="status"></p>
<div class="bar-btns">
<button id="greedy" type="button">Greedy</button>
<button id="opt" type="button">Optimal</button>
<button id="reset" type="button" class="ghost">Reset</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 .6rem; line-height: 1.45; }
.universe { display: flex; gap: .4rem; margin-bottom: .7rem; }
.item { width: 40px; height: 40px; border-radius: 8px; background: #e9ecef; border: 2px solid #ccc; display: flex; align-items: center; justify-content: center; font: 700 16px ui-monospace, monospace; color: #888; transition: all .12s; }
.item.cov { background: #2a9d8f; border-color: #2a9d8f; color: #fff; }
.sets { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: .5rem; }
.set { border: 2px solid #ddd; border-radius: 10px; padding: .5rem .6rem; cursor: pointer; background: #fff; transition: all .12s; user-select: none; }
.set.on { border-color: #457b9d; background: #eaf1f6; box-shadow: 0 2px 8px rgba(69,123,157,.2); }
.set .nm { font-weight: 700; font-size: .95rem; }
.set .el { font: 13px ui-monospace, monospace; color: #555; margin-top: .2rem; }
.status { font-size: 1rem; font-weight: 700; min-height: 1.3em; margin: .5rem 0; }
.status.ok { color: #0a7d33; }
.status.bad { color: #c0392b; }
.bar-btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; 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] ?? ''));
// Set cover: fewest sets whose union is the whole universe. NP-hard; the greedy
// "cover the most uncovered" rule is within ln n of optimal (and that's the best possible).
const UNIVERSE = [1, 2, 3, 4, 5, 6];
const SETS = [
{ id: 'A', el: [1, 2, 3] }, { id: 'B', el: [4, 5, 6] }, { id: 'C', el: [2, 3, 4, 5] },
{ id: 'D', el: [1, 2] }, { id: 'E', el: [5, 6] },
];
let chosen = new Set();
const coveredItems = () => { const s = new Set(); SETS.forEach(S => { if (chosen.has(S.id)) S.el.forEach(e => s.add(e)); }); return s; };
function render() {
const cov = coveredItems();
const uni = document.getElementById('universe'); uni.innerHTML = '';
UNIVERSE.forEach(n => { const d = document.createElement('div'); d.className = 'item' + (cov.has(n) ? ' cov' : ''); d.textContent = n; uni.appendChild(d); });
const box = document.getElementById('sets'); box.innerHTML = '';
SETS.forEach(S => {
const el = document.createElement('div');
el.className = 'set' + (chosen.has(S.id) ? ' on' : '');
el.innerHTML = fmt('<div class="nm">Set %id%</div><div class="el">{ %el% }</div>', { id: S.id, el: S.el.join(', ') });
el.addEventListener('click', () => { chosen.has(S.id) ? chosen.delete(S.id) : chosen.add(S.id); render(); });
box.appendChild(el);
});
const st = document.getElementById('status');
const c = cov.size, k = chosen.size;
if (c < UNIVERSE.length) { st.className = 'status bad'; st.textContent = fmt('%c% of %length% items covered with %k% set(s) — keep going.', { c: c, length: UNIVERSE.length, k: k }); }
else if (k <= 2) { st.className = 'status ok'; st.textContent = fmt('All covered with %k% sets — that’s the minimum! 🎉', { k: k }); }
else { st.className = 'status ok'; st.textContent = fmt('All covered with %k% sets. The minimum is 2 — can you trim it?', { k: k }); }
}
function greedy() {
chosen = new Set(); const cov = new Set();
while (cov.size < UNIVERSE.length) {
let best = null, gain = -1;
for (const S of SETS) { if (chosen.has(S.id)) continue; const g = S.el.filter(e => !cov.has(e)).length; if (g > gain) { gain = g; best = S; } }
if (!best || gain === 0) break;
chosen.add(best.id); best.el.forEach(e => cov.add(e));
}
render();
console.log(fmt('Greedy used %size% sets: %v% (optimal 2).', { size: chosen.size, v: [...chosen].join(', ') }));
}
function optimal() {
let best = null;
for (let m = 1; m < (1 << SETS.length); m++) {
const pick = SETS.filter((_, i) => m & (1 << i));
const u = new Set(); pick.forEach(S => S.el.forEach(e => u.add(e)));
if (u.size === UNIVERSE.length && (best === null || pick.length < best.length)) best = pick;
}
chosen = new Set(best.map(S => S.id)); render();
}
document.getElementById('greedy').addEventListener('click', greedy);
document.getElementById('opt').addEventListener('click', optimal);
document.getElementById('reset').addEventListener('click', () => { chosen = new Set(); render(); });
render();
Then compare. Greedy repeatedly grabs the set that covers the most still-uncovered items — sensible, and it often works. But watch: greedy can be lured by a big overlapping set and end up using more than the optimum. Optimal finds the true fewest. The gap between them is exactly where the difficulty lives.
Comments
Loading comments...