Choose one of the classic languages below, select how many times to pump the middle segment, and watch whether the result stays in the language or escapes it.
<p class="hint">The Pumping Lemma says every long string in a regular language can be split as <b>xyz</b> where the <b>y</b> part can be repeated any number of times and the result stays in the language. Pick a language and a pump count to test it.</p>
<div class="controls">
<label>Language:
<select id="lang">
<option value="anbn">L₁ = {aⁿbⁿ} — NOT regular</option>
<option value="an">L₂ = {aⁿ} — Regular (any n)</option>
<option value="ww">L₃ = {ww} — NOT regular</option>
<option value="even">L₄ = {strings with even a-count} — Regular</option>
</select>
</label>
<label>Pump i =
<input id="pump" type="range" min="0" max="5" value="2" style="vertical-align:middle;width:90px">
<span id="pumpval">2</span>
</label>
</div>
<div class="string-display" id="display"></div>
<div class="verdict" id="verdict"></div>
<div class="explanation" id="explanation"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem 1.2rem; margin-bottom: .9rem; align-items: center; }
label { font-size: .9rem; font-weight: 600; color: #333; }
select, input[type=range] { margin-left: .3rem; }
select { font-size: .88rem; padding: .2rem .4rem; border: 1px solid #bcc5d0; border-radius: 6px; }
.string-display {
font-family: ui-monospace, monospace;
font-size: 1.05rem;
letter-spacing: .12em;
padding: .7rem 1rem;
background: #f0f4f8;
border-radius: 8px;
min-height: 2.4em;
margin-bottom: .5rem;
display: flex;
flex-wrap: wrap;
gap: 2px;
align-items: center;
}
.part { display: inline-block; padding: 2px 0; border-radius: 3px; }
.part-x { color: #1d3557; }
.part-y { background: #ffd60a; color: #333; padding: 2px 4px; border-radius: 4px; }
.part-y.pumped { background: #ff9f1c; }
.part-z { color: #1d3557; }
.sep { color: #999; font-size: .8em; margin: 0 2px; }
.verdict { font-size: 1rem; font-weight: 700; margin: .4rem 0; min-height: 1.4em; }
.verdict.in { color: #0a7d33; }
.verdict.out { color: #c92f3c; }
.explanation { font-size: .85rem; color: #555; line-height: 1.55; }
.label-x { color: #1d3557; font-weight: 700; }
.label-y { color: #b58900; font-weight: 700; }
.label-z { color: #1d3557; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pumping Lemma interactive demo.
// Each language defines: a base string (as {x,y,z} segments), a membership test, and an explanation.
const LANGS = {
anbn: {
name: 'aⁿbⁿ',
// Base string: a^4 b^4, split x=aa, y=aa, z=bbbb
x: 'aa', y: 'aa', z: 'bbbb',
test: s => {
// must be a*b* with equal counts
const m = s.match(/^(a*)(b*)$/);
if (!m) return false;
return m[1].length === m[2].length;
},
explain: (i, inL) => i === 1
? 'i=1: original string aa·aa·bbbb = aaaabbbb ✓ in L₁.'
: inL
? fmt('i=%i%: pumped string has equal a’s and b’s — still in L₁ (coincidence for i=0 when y removed).', { i: i })
: fmt('i=%i%: pumping adds a’s without adding b’s → counts diverge → ∉ L₁. L₁ is NOT regular.', { i: i })
},
an: {
name: 'aⁿ',
// Base string: a^5, split x=a, y=aa, z=aa
x: 'a', y: 'aa', z: 'aa',
test: s => /^a*$/.test(s),
explain: (i, _inL) =>
fmt('i=%i%: pumped string is all a’s — always in L₂. Regular languages survive all pumping.', { i: i })
},
ww: {
name: 'ww',
// Base string: abab, split x=a, y=b, z=ab
x: 'a', y: 'b', z: 'ab',
test: s => {
if (s.length % 2 !== 0) return false;
const half = s.length / 2;
return s.slice(0, half) === s.slice(half);
},
explain: (i, inL) => i === 1
? 'i=1: original abab = ww with w=ab ✓ in L₃.'
: inL
? fmt('i=%i%: pumped string happens to equal ww.', { i: i })
: fmt('i=%i%: pumping shifts the midpoint → first half ≠ second half → ∉ L₃. L₃ is NOT regular.', { i: i })
},
even: {
name: 'even-a',
// Base string: aabaa, split x=aa, y=aa, z=a → hmm, let’s use:
// Base: aabb, x=aa, y=aa, z=’’ — wait, keep it simple
// x=’’, y=’aa’, z=’bb’ so pumped = a^(2i) bb
x: '', y: 'aa', z: 'bb',
test: s => {
const count = (s.match(/a/g) || []).length;
return count % 2 === 0;
},
explain: (i, _inL) =>
fmt('i=%i%: pumped string has %v% a’s (even) + 2 b’s → a-count is even → always in L₄. Regular!', { i: i, v: 2*i })
}
};
const langSel = document.getElementById('lang');
const pumpIn = document.getElementById('pump');
const pumpVal = document.getElementById('pumpval');
const display = document.getElementById('display');
const verdict = document.getElementById('verdict');
const explanEl = document.getElementById('explanation');
function render() {
const key = langSel.value;
const lang = LANGS[key];
const i = parseInt(pumpIn.value, 10);
pumpVal.textContent = i;
const { x, y, z } = lang;
const pumped = y.repeat(i);
const s = x + pumped + z;
// Build display
display.innerHTML = '';
const addSpan = (text, cls, label) => {
if (text.length === 0 && cls !== 'part-y') return;
if (label) {
const lb = document.createElement('span');
lb.className = 'sep';
lb.textContent = label + ':';
display.appendChild(lb);
}
const sp = document.createElement('span');
sp.className = 'part ' + cls + (cls === 'part-y' && i !== 1 ? ' pumped' : '');
sp.textContent = text.length > 0 ? text : '(ε)';
display.appendChild(sp);
};
addSpan(x, 'part-x', 'x');
// Show y repeated
for (let k = 0; k < Math.max(i, 1); k++) {
const lb = document.createElement('span');
lb.className = 'sep';
lb.textContent = k === 0 ? ' y×' + i + ':' : '';
if (k === 0) display.appendChild(lb);
const sp = document.createElement('span');
sp.className = 'part part-y' + (i !== 1 ? ' pumped' : '');
sp.textContent = i === 0 ? '(ε)' : y;
display.appendChild(sp);
if (i === 0) break;
}
addSpan(z, 'part-z', ' z');
const inL = lang.test(s);
verdict.textContent = inL
? fmt('✓ "%s%" ∈ L — pumped string is IN the language', { s: s })
: fmt('✗ "%s%" ∉ L — pumped string EXITS the language', { s: s });
verdict.className = 'verdict ' + (inL ? 'in' : 'out');
explanEl.textContent = lang.explain(i, inL);
}
langSel.addEventListener('change', render);
pumpIn.addEventListener('input', render);
render();
// Node-verifiable assertions (run via node if CODE.JS extracted):
// anbn test
console.assert(LANGS.anbn.test('aabb') === true, 'anbn: aabb in');
console.assert(LANGS.anbn.test('aaabbb') === true, 'anbn: aaabbb in');
console.assert(LANGS.anbn.test('aaaabb') === false,'anbn: aaaabb out');
console.assert(LANGS.anbn.test('') === true, 'anbn: empty in');
// an test
console.assert(LANGS.an.test('aaa') === true, 'an: aaa in');
console.assert(LANGS.an.test('') === true, 'an: empty in');
console.assert(LANGS.an.test('ab') === false, 'an: ab out');
// ww test
console.assert(LANGS.ww.test('abab') === true, 'ww: abab in');
console.assert(LANGS.ww.test('aabb') === false, 'ww: aabb out');
console.assert(LANGS.ww.test('') === true, 'ww: empty in');
console.assert(LANGS.ww.test('aaaa') === true, 'ww: aaaa in');
// even test
console.assert(LANGS.even.test('aabb') === true, 'even: aabb in');
console.assert(LANGS.even.test('abba') === true, 'even: abba in');
console.assert(LANGS.even.test('ab') === false, 'even: ab out');
console.assert(LANGS.even.test('') === true, 'even: empty in');
// Pumping verification: anbn, pump i=2 exits
const L = LANGS.anbn;
const s2 = L.x + L.y.repeat(2) + L.z; // aa+aaaa+bbbb = aaaaaabbbb
console.assert(L.test(s2) === false, 'anbn: pump i=2 exits language');
const s0 = L.x + L.y.repeat(0) + L.z; // aa+bbbb = aabbbb
console.assert(L.test(s0) === false, 'anbn: pump i=0 exits language');
console.log('All assertions passed');
<p class="hint">The Pumping Lemma says every long string in a regular language can be split as <b>xyz</b> where the <b>y</b> part can be repeated any number of times and the result stays in the language. Pick a language and a pump count to test it.</p>
<div class="controls">
<label>Language:
<select id="lang">
<option value="anbn">L₁ = {aⁿbⁿ} — NOT regular</option>
<option value="an">L₂ = {aⁿ} — Regular (any n)</option>
<option value="ww">L₃ = {ww} — NOT regular</option>
<option value="even">L₄ = {strings with even a-count} — Regular</option>
</select>
</label>
<label>Pump i =
<input id="pump" type="range" min="0" max="5" value="2" style="vertical-align:middle;width:90px">
<span id="pumpval">2</span>
</label>
</div>
<div class="string-display" id="display"></div>
<div class="verdict" id="verdict"></div>
<div class="explanation" id="explanation"></div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; }
.hint { font-size: .88rem; color: #444; margin: 0 0 .8rem; line-height: 1.5; }
.controls { display: flex; flex-wrap: wrap; gap: .6rem 1.2rem; margin-bottom: .9rem; align-items: center; }
label { font-size: .9rem; font-weight: 600; color: #333; }
select, input[type=range] { margin-left: .3rem; }
select { font-size: .88rem; padding: .2rem .4rem; border: 1px solid #bcc5d0; border-radius: 6px; }
.string-display {
font-family: ui-monospace, monospace;
font-size: 1.05rem;
letter-spacing: .12em;
padding: .7rem 1rem;
background: #f0f4f8;
border-radius: 8px;
min-height: 2.4em;
margin-bottom: .5rem;
display: flex;
flex-wrap: wrap;
gap: 2px;
align-items: center;
}
.part { display: inline-block; padding: 2px 0; border-radius: 3px; }
.part-x { color: #1d3557; }
.part-y { background: #ffd60a; color: #333; padding: 2px 4px; border-radius: 4px; }
.part-y.pumped { background: #ff9f1c; }
.part-z { color: #1d3557; }
.sep { color: #999; font-size: .8em; margin: 0 2px; }
.verdict { font-size: 1rem; font-weight: 700; margin: .4rem 0; min-height: 1.4em; }
.verdict.in { color: #0a7d33; }
.verdict.out { color: #c92f3c; }
.explanation { font-size: .85rem; color: #555; line-height: 1.55; }
.label-x { color: #1d3557; font-weight: 700; }
.label-y { color: #b58900; font-weight: 700; }
.label-z { color: #1d3557; font-weight: 700; }
const fmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Pumping Lemma interactive demo.
// Each language defines: a base string (as {x,y,z} segments), a membership test, and an explanation.
const LANGS = {
anbn: {
name: 'aⁿbⁿ',
// Base string: a^4 b^4, split x=aa, y=aa, z=bbbb
x: 'aa', y: 'aa', z: 'bbbb',
test: s => {
// must be a*b* with equal counts
const m = s.match(/^(a*)(b*)$/);
if (!m) return false;
return m[1].length === m[2].length;
},
explain: (i, inL) => i === 1
? 'i=1: original string aa·aa·bbbb = aaaabbbb ✓ in L₁.'
: inL
? fmt('i=%i%: pumped string has equal a’s and b’s — still in L₁ (coincidence for i=0 when y removed).', { i: i })
: fmt('i=%i%: pumping adds a’s without adding b’s → counts diverge → ∉ L₁. L₁ is NOT regular.', { i: i })
},
an: {
name: 'aⁿ',
// Base string: a^5, split x=a, y=aa, z=aa
x: 'a', y: 'aa', z: 'aa',
test: s => /^a*$/.test(s),
explain: (i, _inL) =>
fmt('i=%i%: pumped string is all a’s — always in L₂. Regular languages survive all pumping.', { i: i })
},
ww: {
name: 'ww',
// Base string: abab, split x=a, y=b, z=ab
x: 'a', y: 'b', z: 'ab',
test: s => {
if (s.length % 2 !== 0) return false;
const half = s.length / 2;
return s.slice(0, half) === s.slice(half);
},
explain: (i, inL) => i === 1
? 'i=1: original abab = ww with w=ab ✓ in L₃.'
: inL
? fmt('i=%i%: pumped string happens to equal ww.', { i: i })
: fmt('i=%i%: pumping shifts the midpoint → first half ≠ second half → ∉ L₃. L₃ is NOT regular.', { i: i })
},
even: {
name: 'even-a',
// Base string: aabaa, split x=aa, y=aa, z=a → hmm, let’s use:
// Base: aabb, x=aa, y=aa, z=’’ — wait, keep it simple
// x=’’, y=’aa’, z=’bb’ so pumped = a^(2i) bb
x: '', y: 'aa', z: 'bb',
test: s => {
const count = (s.match(/a/g) || []).length;
return count % 2 === 0;
},
explain: (i, _inL) =>
fmt('i=%i%: pumped string has %v% a’s (even) + 2 b’s → a-count is even → always in L₄. Regular!', { i: i, v: 2*i })
}
};
const langSel = document.getElementById('lang');
const pumpIn = document.getElementById('pump');
const pumpVal = document.getElementById('pumpval');
const display = document.getElementById('display');
const verdict = document.getElementById('verdict');
const explanEl = document.getElementById('explanation');
function render() {
const key = langSel.value;
const lang = LANGS[key];
const i = parseInt(pumpIn.value, 10);
pumpVal.textContent = i;
const { x, y, z } = lang;
const pumped = y.repeat(i);
const s = x + pumped + z;
// Build display
display.innerHTML = '';
const addSpan = (text, cls, label) => {
if (text.length === 0 && cls !== 'part-y') return;
if (label) {
const lb = document.createElement('span');
lb.className = 'sep';
lb.textContent = label + ':';
display.appendChild(lb);
}
const sp = document.createElement('span');
sp.className = 'part ' + cls + (cls === 'part-y' && i !== 1 ? ' pumped' : '');
sp.textContent = text.length > 0 ? text : '(ε)';
display.appendChild(sp);
};
addSpan(x, 'part-x', 'x');
// Show y repeated
for (let k = 0; k < Math.max(i, 1); k++) {
const lb = document.createElement('span');
lb.className = 'sep';
lb.textContent = k === 0 ? ' y×' + i + ':' : '';
if (k === 0) display.appendChild(lb);
const sp = document.createElement('span');
sp.className = 'part part-y' + (i !== 1 ? ' pumped' : '');
sp.textContent = i === 0 ? '(ε)' : y;
display.appendChild(sp);
if (i === 0) break;
}
addSpan(z, 'part-z', ' z');
const inL = lang.test(s);
verdict.textContent = inL
? fmt('✓ "%s%" ∈ L — pumped string is IN the language', { s: s })
: fmt('✗ "%s%" ∉ L — pumped string EXITS the language', { s: s });
verdict.className = 'verdict ' + (inL ? 'in' : 'out');
explanEl.textContent = lang.explain(i, inL);
}
langSel.addEventListener('change', render);
pumpIn.addEventListener('input', render);
render();
// Node-verifiable assertions (run via node if CODE.JS extracted):
// anbn test
console.assert(LANGS.anbn.test('aabb') === true, 'anbn: aabb in');
console.assert(LANGS.anbn.test('aaabbb') === true, 'anbn: aaabbb in');
console.assert(LANGS.anbn.test('aaaabb') === false,'anbn: aaaabb out');
console.assert(LANGS.anbn.test('') === true, 'anbn: empty in');
// an test
console.assert(LANGS.an.test('aaa') === true, 'an: aaa in');
console.assert(LANGS.an.test('') === true, 'an: empty in');
console.assert(LANGS.an.test('ab') === false, 'an: ab out');
// ww test
console.assert(LANGS.ww.test('abab') === true, 'ww: abab in');
console.assert(LANGS.ww.test('aabb') === false, 'ww: aabb out');
console.assert(LANGS.ww.test('') === true, 'ww: empty in');
console.assert(LANGS.ww.test('aaaa') === true, 'ww: aaaa in');
// even test
console.assert(LANGS.even.test('aabb') === true, 'even: aabb in');
console.assert(LANGS.even.test('abba') === true, 'even: abba in');
console.assert(LANGS.even.test('ab') === false, 'even: ab out');
console.assert(LANGS.even.test('') === true, 'even: empty in');
// Pumping verification: anbn, pump i=2 exits
const L = LANGS.anbn;
const s2 = L.x + L.y.repeat(2) + L.z; // aa+aaaa+bbbb = aaaaaabbbb
console.assert(L.test(s2) === false, 'anbn: pump i=2 exits language');
const s0 = L.x + L.y.repeat(0) + L.z; // aa+bbbb = aabbbb
console.assert(L.test(s0) === false, 'anbn: pump i=0 exits language');
console.log('All assertions passed');
Notice the asymmetry. For {anbn} (equal numbers of a's and b's), pumping the middle segment adds extra a's without adding b's — the counts diverge and the string leaves the language. For a regular language like {an} (any number of a's), pumping just adds more a's and the string always stays in. That difference is the lemma at work: if any valid pumping decomposition ever exits the language, no finite machine can recognize it.
Comments
Loading comments...