using System.Text; namespace HuffmanEditor; public static class Huffman { public static byte[] EncodeString(string str) { return GenerateBytes(str); } public static string DecodeString(byte[] bytes) { return DecypherBytes(bytes); } private static HuffmanNode GenerateTree(string str) { var frequencies = new int[65536]; foreach (var character in str) frequencies[character]++; var priorityQueue = new PriorityQueue(); for (var i = 0; i < frequencies.Length; i++) if (frequencies[i] != 0) priorityQueue.Enqueue(new HuffmanNode((char)i, frequencies[i]), frequencies[i]); while (priorityQueue.Count > 1) { var left = priorityQueue.Dequeue(); var right = priorityQueue.Dequeue(); priorityQueue.Enqueue(new HuffmanNode(left, right), left.Frequency + right.Frequency); } return priorityQueue.Dequeue(); } private static SortedDictionary GenerateLengthDictionary(HuffmanNode node) { var dict = new SortedDictionary(); void TraverseDFS(HuffmanNode node, ushort depth) { if (node == null) return; if (node.Symbol != null) { dict.Add(node.Symbol.Value, depth); return; } TraverseDFS(node.Left!, (ushort)(depth + 1)); TraverseDFS(node.Right!, (ushort)(depth + 1)); } TraverseDFS(node.Left!, 1); TraverseDFS(node.Right!, 1); return dict; } private static Dictionary RecreateDictionary(SortedDictionary lenDict) { var sorted = lenDict.OrderBy(x => x.Value).ThenBy(x => x.Key).ToList(); var codes = new Dictionary(); var currentCode = new HuffmanCode { Code = 0, Length = 0 }; foreach (var code in sorted) { if (code.Value > currentCode.Length) { currentCode.Code <<= (code.Value - currentCode.Length); currentCode.Length = code.Value; } codes.Add(code.Key, currentCode); currentCode.Code++; } return codes; } private static HuffmanNode RecreateTree(SortedDictionary lenDict) { var dict = RecreateDictionary(lenDict); var root = new HuffmanNode(); foreach (var code in dict) { var currentNode = root; for (var i = code.Value.Length - 1; i >= 0; i--) { if (((code.Value.Code >> i) & 1) == 1) { currentNode.Right ??= new HuffmanNode(); currentNode = currentNode.Right; } else { currentNode.Left ??= new HuffmanNode(); currentNode = currentNode.Left; } } currentNode.Symbol = code.Key; } return root; } private static byte[] GenerateBytes(string str) { var lenDict = GenerateLengthDictionary(GenerateTree(RSFFilter(str))); //var lenDict = GenerateLengthDictionary(GenerateTree(str)); var bytes = new List { (byte)(lenDict.Count >> 8), (byte)(lenDict.Count & 0xFF), }; foreach (var kvp in lenDict) bytes.AddRange( [ (byte)(kvp.Key >> 8), (byte)(kvp.Key & 0xFF), (byte)(kvp.Value >> 8), (byte)(kvp.Value & 0xFF), ] ); bytes.AddRange( [ (byte)(str.Length >> 24), (byte)(str.Length >> 16 & 0xFF), (byte)(str.Length >> 8 & 0xFF), (byte)(str.Length & 0xFF), ] ); var dict = RecreateDictionary(lenDict); byte writeBuffer = 0, counter = 0; foreach (var code in str) { var thisCode = dict[code]; for (var i = 0; i < thisCode.Length; i++) { writeBuffer = (byte)((writeBuffer << 1) | ((thisCode.Code >> (thisCode.Length - i - 1)) & 1)); counter++; if (counter == 8) { counter = 0; bytes.Add(writeBuffer); writeBuffer = 0; } } } while (true) { writeBuffer = (byte)(writeBuffer << 1); counter++; if (counter == 8) { bytes.Add(writeBuffer); break; } } return bytes.ToArray(); } private static string DecypherBytes(byte[] bytes) { var currentByte = 0; var letters = bytes[currentByte++] << 8 | bytes[currentByte++]; var result = string.Empty; var lenDict = new SortedDictionary(); for (var i = 0; i < letters; i++) lenDict.Add((char)(bytes[currentByte++] << 8 | bytes[currentByte++]), (ushort)(bytes[currentByte++] << 8 | bytes[currentByte++])); var characters = bytes[currentByte++] << 24 | bytes[currentByte++] << 16 | bytes[currentByte++] << 8 | bytes[currentByte++]; var tree = RecreateTree(lenDict); var currentNode = tree; for (var i = 0; i < characters - 1; ) { var thisByte = bytes[currentByte++]; for (var j = 0; j < 8; j++) { if ((thisByte >> (7 - j) & 0x01) == 1) currentNode = currentNode.Right; else currentNode = currentNode.Left; if (currentNode == null) throw new ArgumentException("IDK man you done bad"); if (currentNode.Symbol != null) { result += currentNode.Symbol.Value; currentNode = tree; i++; } } } return RSFUnfilter(result); //return result; } private static string RSFFilter(string input) { if (string.IsNullOrEmpty(input)) return input; // Initialize with input length to reduce resizing overhead var sb = new StringBuilder(input.Length); var i = 0; while (i < input.Length) { var currentChar = input[i]; var count = 1; // Count consecutive identical characters while (i + count < input.Length && input[i + count] == currentChar) { count++; } // Apply transformation if repeat count > 3 (4 or more occurrences) if (count > 3) { sb.Append('\uE000'); sb.Append(count); sb.Append(currentChar); } else { // Append the characters as-is (1, 2, or 3 times) sb.Append(currentChar, count); } i += count; } return sb.ToString(); } private static string RSFUnfilter(string input) { if (string.IsNullOrEmpty(input)) return input; var sb = new StringBuilder(); int i = 0; while (i < input.Length) { if (input[i] == '\uE000') { i++; // Skip the marker int start = i; // Parse the numeric repeatCount while (i < input.Length && char.IsDigit(input[i])) { i++; } if (int.TryParse(input.AsSpan(start, i - start), out var count) && i < input.Length) { var repeatChar = input[i]; sb.Append(repeatChar, count); i++; // Move past the character being repeated } } else { sb.Append(input[i]); i++; } } return sb.ToString(); } private struct HuffmanCode { public int Code; public ushort Length; } } public class HuffmanNode : IComparable { public char? Symbol { get; set; } public int Frequency { get; set; } public HuffmanNode? Left { get; set; } public HuffmanNode? Right { get; set; } public HuffmanNode(char symbol, int frequency) { Symbol = symbol; Frequency = frequency; } public HuffmanNode(HuffmanNode left, HuffmanNode right) { Symbol = null; Left = left; Right = right; Frequency = left.Frequency + right.Frequency; } public HuffmanNode() { Frequency = 0; Symbol = null; Left = null; Right = null; } public int CompareTo(HuffmanNode? other) { if (other == null) return 1; return Frequency.CompareTo(other.Frequency); } }