"use strict"; const $ = id => document.getElementById(id); const esc = s => String(s).replace(/&/g, "&").replace(//g, ">"); const rep = s => JSON.stringify(s).replace(/\\n/g, "\\n"); function lzwCompress(text) { const log = []; if (!text) return { codes: [], alphabet: [], dict: {}, log: ["пустой ввод"] }; const alphabet = Array.from(new Set(text.split(""))).sort(); const dict = new Map(); alphabet.forEach((ch, i) => dict.set(ch, i)); let nextCode = alphabet.length; log.push("Начальный словарь (одиночные символы):"); alphabet.forEach((ch, i) => { log.push(` код ${String(i).padStart(3)} <- ${esc(rep(ch))}`); }); log.push(""); log.push("Журнал добавлений при сжатии:"); log.push("" + "─".repeat(52) + ""); const codes = []; let w = ""; for (const c of text) { const wc = w + c; if (dict.has(wc)) { w = wc; } else { codes.push(dict.get(w)); dict.set(wc, nextCode); log.push(` + код ${String(nextCode).padStart(3)} <- ${esc(rep(wc))} (вывод: код ${String(dict.get(w)).padStart(3)} для ${esc(rep(w))})`); nextCode++; w = c; } } if (w !== "") { codes.push(dict.get(w)); log.push(` финальный вывод: код ${String(dict.get(w)).padStart(3)} для ${esc(rep(w))}`); } const dictPlain = {}; dict.forEach((v, k) => { dictPlain[k] = v; }); return { codes, alphabet, dict: dictPlain, log }; } function lzwDecompress(codes, alphabet) { const log = []; log.push("Декодеру передано ТОЛЬКО:"); log.push(` • поток кодов : [${codes.join(", ")}]`); log.push(` • алфавит : ${esc(rep(alphabet.join("")))}`); log.push(""); log.push("Декодеру НЕ передан словарь компрессора —"); log.push("он восстанавливает его САМ, шаг за шагом,"); log.push("по тем же правилам, что использовал компрессор."); log.push(""); log.push("" + "─".repeat(52) + ""); log.push("ШАГ 0. Словарь инициализируется ТОЛЬКО одиночными символами:"); log.push("" + "─".repeat(52) + ""); const dict = new Map(); alphabet.forEach((ch, i) => { dict.set(i, ch); log.push(` код ${String(i).padStart(3)} <- ${esc(rep(ch))}`); }); let nextCode = alphabet.length; log.push(""); log.push("Журнал распаковки:"); log.push("" + "═".repeat(52) + ""); const result = []; let prev = null; let out = ""; codes.forEach((code, idx) => { log.push(""); log.push(` Шаг ${idx + 1}: получен код ${code}`); let entry; if (dict.has(code)) { entry = dict.get(code); log.push(` код ${code} найден в словаре -> ${esc(rep(entry))}`); } else if (code === nextCode && prev !== null) { entry = prev + prev[0]; log.push(` код ${code} ЕЩЁ НЕ в словаре -> случай KwKwK`); log.push(` достраиваем сами: ${esc(rep(prev))} + ${esc(rep(prev[0]))} -> ${esc(rep(entry))}`); } else { log.push(` Некорректный код ${code}!`); throw new Error("Некорректный код " + code); } result.push(entry); out += entry; if (prev !== null) { const newPhrase = prev + entry[0]; dict.set(nextCode, newPhrase); log.push(` НОВАЯ ФРАЗА (строим сами):`); log.push(` + код ${String(nextCode).padStart(3)} <- ${esc(rep(newPhrase))}`); nextCode++; } prev = entry; log.push(` накоплено: ${esc(rep(out))}`); }); log.push(""); log.push("" + "═".repeat(52) + ""); log.push("ИТОГ: словарь восстановлен полностью, без его передачи."); return { text: out, log }; } function buildStats(text, codes) { const originalBits = text.length * 8; const maxCode = codes.length ? Math.max(...codes) : 1; const bitsPerCode = Math.max(1, maxCode.toString(2).length); const compressedBits = codes.length * bitsPerCode; const ratio = compressedBits ? originalBits / compressedBits : 0; const percent = originalBits ? 100 * compressedBits / originalBits : 0; const lines = text.length ? (text.split("\n").length) : 0; const uniq = new Set(text.split("")).size; const items = [ ["Символов в тексте", text.length], ["Строк в тексте", lines], ["Уникальных символов", uniq], ["Исходный размер", originalBits + " бит (" + (originalBits / 8).toFixed(0) + " байт)"], ["Кодов на выходе", codes.length], ["Разрядность кода", bitsPerCode + " бит"], ["Размер сжатых данных", compressedBits + " бит (" + (compressedBits / 8).toFixed(2) + " байт)"], ["Коэффициент сжатия", ratio.toFixed(2) + "x", true], ["Доля от исходного", percent.toFixed(1) + "%"], ["Экономия", (100 - percent).toFixed(1) + "%", true], ]; return items.map(([k, v, ok]) => `
${k}
${esc(v)}
`).join(""); } function run() { const text = $("input").value; if (!text) { $("compLog").innerHTML = "Пустой ввод — нечего сжимать."; $("contract").innerHTML = "—"; $("decompLog").innerHTML = "—"; $("origView").textContent = "—"; $("restView").textContent = "—"; $("verdict").innerHTML = "—"; $("stats").innerHTML = "—"; return; } const comp = lzwCompress(text); $("compLog").innerHTML = comp.log.join("\n"); const totalEntries = Object.keys(comp.dict).length; const longEntries = Object.keys(comp.dict).filter(k => Array.from(k).length >= 2).length; $("contract").innerHTML = [ `✔ поток кодов : [${comp.codes.join(", ")}]`, `✔ алфавит : ${esc(rep(comp.alphabet.join("")))}`, `✘ словарь фраз длиной >= 2 — НЕ передаётся`, ` (компрессор построил ${totalEntries} записей,`, ` из них фраз >= 2 символов: ${longEntries})`, ].join("\n"); let decomp; try { decomp = lzwDecompress(comp.codes, comp.alphabet); } catch (e) { $("decompLog").innerHTML = "Ошибка распаковки: " + esc(e.message) + ""; return; } $("decompLog").innerHTML = decomp.log.join("\n"); $("origView").textContent = text; $("restView").textContent = decomp.text; const ok = decomp.text === text; $("verdict").innerHTML = ok ? "OK" : "ОШИБКА"; $("stats").innerHTML = buildStats(text, comp.codes); } function initLzw() { const LAWS = [ 'первый закон: Робот не может причинить вред человеку или своим бездействием допустить, чтобы человеку был причинён вред.', 'второй закон: Робот должен повиноваться всем приказам, которые даёт человек, кроме тех случаев, когда эти приказы противоречат Первому закону.', 'третий закон: Робот должен заботиться о своей безопасности в той мере, в которой это не противоречит Первому или Второму законам.' ]; $("example").addEventListener('click', () => { const randomLaw = LAWS[Math.floor(Math.random() * LAWS.length)]; $("input").value = randomLaw; run(); }); ; if (!$("run")) return; $("run").addEventListener("click", run); $("clear").addEventListener("click", () => { $("input").value = ""; $("input").focus(); run(); }); $("input").addEventListener("keydown", e => { if (e.ctrlKey && e.key === "Enter") { e.preventDefault(); run(); } }); run(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initLzw); } else { initLzw(); }