"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]) => `