<p class="hint">A hidden pattern produces a row of bits. Two learners guess the <b>next</b> bit at each step: <b>Stay</b> bets it repeats the last bit, <b>Flip</b> bets it alternates. Pick a pattern and run them — then average over <b>all</b> patterns.</p>
<div class="row">
<label>Hidden pattern:
<select id="pat">
<option value="repeat">Mostly repeats (00000111)</option>
<option value="alt">Alternates (01010101)</option>
<option value="rand">Irregular (01101001)</option>
</select>
</label>
</div>
<div id="bits" class="bits"></div>
<div class="score">
<span class="tag stay">Stay: <b id="sStay">0</b></span>
<span class="tag flip">Flip: <b id="sFlip">0</b></span>
</div>
<div class="status" id="status">Pick a pattern and press Run.</div>
<div class="btns">
<button id="run" type="button">Run on this pattern</button>
<button id="avg" type="button">Average over ALL patterns</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 .7rem; line-height: 1.45; }
.row { margin: .3rem 0 .6rem; font-size: .92rem; }
select { font: inherit; padding: .2rem .35rem; border-radius: 6px; border: 1px solid #adb1b8; }
.bits { display: flex; flex-wrap: wrap; gap: 5px; margin: .5rem 0; min-height: 40px; }
.bit { width: 34px; height: 38px; display: flex; align-items: center; justify-content: center;
font: 700 16px ui-monospace, monospace; border-radius: 7px; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; position: relative; }
.bit.sw { box-shadow: inset 0 -4px 0 #0a7d33; }
.bit.fw { box-shadow: inset 0 -4px 0 #c92f3c; }
.bit.both { box-shadow: inset 0 -4px 0 #6b7280; }
.score { display: flex; gap: .6rem; margin: .4rem 0; }
.tag { font: 600 .92rem system-ui; padding: .25rem .6rem; border-radius: 999px; }
.tag.stay { background: #def0e2; color: #0a7d33; }
.tag.flip { background: #fadfe2; color: #c92f3c; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// No Free Lunch in miniature. A hidden pattern is a bit string. At each step the
// learners predict the NEXT bit from the LAST seen bit:
// Stay -> predicts the same bit (repeat); Flip -> predicts the opposite.
// A learner scores 1 each time its prediction matches the true next bit.
// Claim (NFL): averaged over ALL 2^n bit strings, Stay and Flip score EXACTLY
// the same total. Every string where Stay wins is mirrored by one where Flip does.
const PATTERNS = {
repeat: [0,0,0,0,0,1,1,1],
alt: [0,1,0,1,0,1,0,1],
rand: [0,1,1,0,1,0,0,1]
};
// Score one string. Predictions start at index 1 (need a previous bit).
function score(bits){
let stay=0, flip=0;
for(let i=1;i<bits.length;i++){
const prev=bits[i-1], truth=bits[i];
if(prev===truth) stay++; // Stay predicted prev, was right
if((1-prev)===truth) flip++; // Flip predicted opposite, was right
}
return {stay, flip};
}
function render(bits, res){
const wrap=document.getElementById('bits'); wrap.innerHTML='';
bits.forEach((b,i)=>{
const d=document.createElement('div'); d.className='bit'; d.textContent=b;
if(i>=1){
const prev=bits[i-1];
const sRight = (prev===b), fRight = ((1-prev)===b);
if(sRight&&fRight) d.classList.add('both');
else if(sRight) d.classList.add('sw');
else if(fRight) d.classList.add('fw');
}
wrap.appendChild(d);
});
document.getElementById('sStay').textContent=res?res.stay:0;
document.getElementById('sFlip').textContent=res?res.flip:0;
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+(cls||''); }
function run(){
const bits=PATTERNS[document.getElementById('pat').value];
const res=score(bits); render(bits,res);
const lead = res.stay>res.flip ? 'Stay wins this world.'
: res.flip>res.stay ? 'Flip wins this world.'
: 'Tie on this world.';
setStatus(lead, res.stay===res.flip?'':'ok');
}
// Enumerate every n-bit string; sum each learner's score over all of them.
function average(){
const n=8, total=1<<n; let sumStay=0, sumFlip=0;
for(let m=0;m<total;m++){
const bits=[]; for(let i=0;i<n;i++) bits.push((m>>i)&1);
const r=score(bits); sumStay+=r.stay; sumFlip+=r.flip;
}
document.getElementById('sStay').textContent=sumStay;
document.getElementById('sFlip').textContent=sumFlip;
const eq = sumStay===sumFlip;
setStatus(fmt('Over all %total% patterns: Stay=%sumStay%, Flip=%sumFlip%%v%', { total: total, sumStay: sumStay, sumFlip: sumFlip, v: ' — ' + (eq?'an exact tie. No free lunch.':'(should be equal!)') }), eq?'ok':'');
console.log('All', total, 'patterns -> Stay', sumStay, 'Flip', sumFlip, 'equal:', eq);
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('avg').addEventListener('click', average);
document.getElementById('reset').addEventListener('click', ()=>{
render(PATTERNS[document.getElementById('pat').value], null);
setStatus('Pick a pattern and press Run.','');
});
render(PATTERNS.repeat, null);
<p class="hint">A hidden pattern produces a row of bits. Two learners guess the <b>next</b> bit at each step: <b>Stay</b> bets it repeats the last bit, <b>Flip</b> bets it alternates. Pick a pattern and run them — then average over <b>all</b> patterns.</p>
<div class="row">
<label>Hidden pattern:
<select id="pat">
<option value="repeat">Mostly repeats (00000111)</option>
<option value="alt">Alternates (01010101)</option>
<option value="rand">Irregular (01101001)</option>
</select>
</label>
</div>
<div id="bits" class="bits"></div>
<div class="score">
<span class="tag stay">Stay: <b id="sStay">0</b></span>
<span class="tag flip">Flip: <b id="sFlip">0</b></span>
</div>
<div class="status" id="status">Pick a pattern and press Run.</div>
<div class="btns">
<button id="run" type="button">Run on this pattern</button>
<button id="avg" type="button">Average over ALL patterns</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 .7rem; line-height: 1.45; }
.row { margin: .3rem 0 .6rem; font-size: .92rem; }
select { font: inherit; padding: .2rem .35rem; border-radius: 6px; border: 1px solid #adb1b8; }
.bits { display: flex; flex-wrap: wrap; gap: 5px; margin: .5rem 0; min-height: 40px; }
.bit { width: 34px; height: 38px; display: flex; align-items: center; justify-content: center;
font: 700 16px ui-monospace, monospace; border-radius: 7px; background: #e8eef3;
color: #1d3557; border: 1px solid #cdd9e3; position: relative; }
.bit.sw { box-shadow: inset 0 -4px 0 #0a7d33; }
.bit.fw { box-shadow: inset 0 -4px 0 #c92f3c; }
.bit.both { box-shadow: inset 0 -4px 0 #6b7280; }
.score { display: flex; gap: .6rem; margin: .4rem 0; }
.tag { font: 600 .92rem system-ui; padding: .25rem .6rem; border-radius: 999px; }
.tag.stay { background: #def0e2; color: #0a7d33; }
.tag.flip { background: #fadfe2; color: #c92f3c; }
.status { font-size: 1rem; font-weight: 600; margin: .5rem 0; min-height: 1.4em; }
.status.ok { color: #0a7d33; }
.btns { display: flex; gap: .5rem; flex-wrap: wrap; }
button { font: 600 14px system-ui, sans-serif; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// No Free Lunch in miniature. A hidden pattern is a bit string. At each step the
// learners predict the NEXT bit from the LAST seen bit:
// Stay -> predicts the same bit (repeat); Flip -> predicts the opposite.
// A learner scores 1 each time its prediction matches the true next bit.
// Claim (NFL): averaged over ALL 2^n bit strings, Stay and Flip score EXACTLY
// the same total. Every string where Stay wins is mirrored by one where Flip does.
const PATTERNS = {
repeat: [0,0,0,0,0,1,1,1],
alt: [0,1,0,1,0,1,0,1],
rand: [0,1,1,0,1,0,0,1]
};
// Score one string. Predictions start at index 1 (need a previous bit).
function score(bits){
let stay=0, flip=0;
for(let i=1;i<bits.length;i++){
const prev=bits[i-1], truth=bits[i];
if(prev===truth) stay++; // Stay predicted prev, was right
if((1-prev)===truth) flip++; // Flip predicted opposite, was right
}
return {stay, flip};
}
function render(bits, res){
const wrap=document.getElementById('bits'); wrap.innerHTML='';
bits.forEach((b,i)=>{
const d=document.createElement('div'); d.className='bit'; d.textContent=b;
if(i>=1){
const prev=bits[i-1];
const sRight = (prev===b), fRight = ((1-prev)===b);
if(sRight&&fRight) d.classList.add('both');
else if(sRight) d.classList.add('sw');
else if(fRight) d.classList.add('fw');
}
wrap.appendChild(d);
});
document.getElementById('sStay').textContent=res?res.stay:0;
document.getElementById('sFlip').textContent=res?res.flip:0;
}
function setStatus(t,cls){ const s=document.getElementById('status'); s.textContent=t; s.className='status '+(cls||''); }
function run(){
const bits=PATTERNS[document.getElementById('pat').value];
const res=score(bits); render(bits,res);
const lead = res.stay>res.flip ? 'Stay wins this world.'
: res.flip>res.stay ? 'Flip wins this world.'
: 'Tie on this world.';
setStatus(lead, res.stay===res.flip?'':'ok');
}
// Enumerate every n-bit string; sum each learner's score over all of them.
function average(){
const n=8, total=1<<n; let sumStay=0, sumFlip=0;
for(let m=0;m<total;m++){
const bits=[]; for(let i=0;i<n;i++) bits.push((m>>i)&1);
const r=score(bits); sumStay+=r.stay; sumFlip+=r.flip;
}
document.getElementById('sStay').textContent=sumStay;
document.getElementById('sFlip').textContent=sumFlip;
const eq = sumStay===sumFlip;
setStatus(fmt('Over all %total% patterns: Stay=%sumStay%, Flip=%sumFlip%%v%', { total: total, sumStay: sumStay, sumFlip: sumFlip, v: ' — ' + (eq?'an exact tie. No free lunch.':'(should be equal!)') }), eq?'ok':'');
console.log('All', total, 'patterns -> Stay', sumStay, 'Flip', sumFlip, 'equal:', eq);
}
document.getElementById('run').addEventListener('click', run);
document.getElementById('avg').addEventListener('click', average);
document.getElementById('reset').addEventListener('click', ()=>{
render(PATTERNS[document.getElementById('pat').value], null);
setStatus('Pick a pattern and press Run.','');
});
render(PATTERNS.repeat, null);
Switch the hidden pattern and the lead changes hands. The real point is the last button: average over all patterns. Let the computer enumerate every possible bit sequence and score both learners on each. Their totals come out identical — every advantage Stay has on one world is exactly cancelled by a world where Flip wins. That tie is the No Free Lunch theorem in miniature.
Comments
Loading comments...