katya / src /utils /encryption /shannon-fano.ts
esubtelnik's picture
Add download endpoints for visitors, employees, and books; update Swagger documentation
2200342
export interface FrequencyNode {
char: string;
freq: number;
firstOccurrence: number;
}
export interface CodeTableEntry {
char: string;
code: string;
}
export function getFrequencies(text: string): FrequencyNode[] {
const freqMap = new Map<string, { freq: number; firstOccurrence: number }>();
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (!freqMap.has(char)) {
freqMap.set(char, { freq: 0, firstOccurrence: i });
}
freqMap.get(char)!.freq++;
}
const nodes: FrequencyNode[] = [];
freqMap.forEach((value, char) => {
nodes.push({
char,
freq: value.freq,
firstOccurrence: value.firstOccurrence,
});
});
nodes.sort((a, b) => {
if (b.freq !== a.freq) {
return b.freq - a.freq;
}
return a.firstOccurrence - b.firstOccurrence;
});
return nodes;
}
export function buildShannonFanoTree(
nodes: FrequencyNode[],
codeTable: Map<string, string>,
prefix: string = ""
): void {
if (nodes.length === 0) return;
if (nodes.length === 1) {
codeTable.set(nodes[0].char, prefix || "0");
return;
}
const totalFreq = nodes.reduce((sum, node) => sum + node.freq, 0);
const halfFreq = totalFreq / 2;
let leftSum = 0;
let splitIndex = 0;
for (let i = 0; i < nodes.length; i++) {
leftSum += nodes[i].freq;
if (leftSum >= halfFreq) {
const leftSumWithout = leftSum - nodes[i].freq;
const diffWith = Math.abs(leftSum - halfFreq);
const diffWithout = Math.abs(leftSumWithout - halfFreq);
if (diffWith <= diffWithout) {
splitIndex = i + 1;
} else {
splitIndex = i;
}
break;
}
}
if (splitIndex === 0) {
splitIndex = nodes.length - 1;
}
const leftNodes = nodes.slice(0, splitIndex);
const rightNodes = nodes.slice(splitIndex);
buildShannonFanoTree(leftNodes, codeTable, prefix + "0");
buildShannonFanoTree(rightNodes, codeTable, prefix + "1");
}
export function shannonFanoEncode(text: string): { encoded: string; table: CodeTableEntry[] } {
if (!text) return { encoded: "", table: [] };
const frequencies = getFrequencies(text);
const codeTable = new Map<string, string>();
buildShannonFanoTree(frequencies, codeTable);
let encoded = "";
for (const char of text) {
const code = codeTable.get(char);
if (!code) {
console.error(`Character '${char}' (code: ${char.charCodeAt(0)}) not found in the code table!`);
}
encoded += code || "";
}
const table: CodeTableEntry[] = [];
codeTable.forEach((code, char) => {
table.push({ char, code });
});
return { encoded, table };
}
export function shannonFanoDecode(encoded: string, table: CodeTableEntry[]): string {
if (!encoded || table.length === 0) return "";
const invalidEntries = table.filter(e => !e.char || !e.code);
if (invalidEntries.length > 0) {
console.error("Found invalid entries in the table:", invalidEntries);
}
const reverseTable = new Map<string, string>();
table.forEach(({ char, code }) => {
if (char && code) {
if (reverseTable.has(code)) {
console.error(`Duplicate code '${code}' for characters '${reverseTable.get(code)}' and '${char}'`);
}
reverseTable.set(code, char);
}
});
console.log("Size of the reverse table:", reverseTable.size);
console.log("Length of the encoded string:", encoded.length);
let decoded = "";
let currentCode = "";
let position = 0;
for (const bit of encoded) {
currentCode += bit;
position++;
if (reverseTable.has(currentCode)) {
const char = reverseTable.get(currentCode)!;
decoded += char;
currentCode = "";
}
if (currentCode.length > 20) {
console.error(`Too long code at position ${position}: '${currentCode}'`);
console.error(`Decoded up to this point: '${decoded}'`);
break;
}
}
if (currentCode.length > 0) {
console.error(`Unprocessed code: '${currentCode}' (length: ${currentCode.length})`);
console.error("Available codes in the table:", Array.from(reverseTable.keys()).slice(0, 10));
}
return decoded;
}