/// <summary> /// ハフマン圧縮 /// </summary> /// <param name="data">圧縮前データ</param> /// <returns>圧縮後データ</returns> internal static byte[] Encode(byte[] data) { // 圧縮前データのサイズ確認(32bit=4byteの数値) byte[] size = BitConverter.GetBytes(data.Count()); // byteの中のバイト別出現回数を集計 // ハフマン木を作成 HaffmanNode root = GenerateHaffmanTree(GetFrequency(data)); // debug if (debugMode) { DebugHaffmanTree(root, 0, ""); } // ハフマンコード表を作成 Dictionary <byte, bool[]> haffmanDict = GetHaffmanCodeTable(root); // debug if (debugMode) { DebugHaffmanTable(haffmanDict); } // ハフマン符号化(匿名メソッド使用) IEnumerable <bool[]> haffmanCodes = data.Select(b => { return(haffmanDict[b]); }); // ハフマン木をbit化 IEnumerable <bool> treeBits = ConvertHaffmanTreeToBits(root); // 書き込む全ビットデータからバイトデータ取得 // sizeデータの後ろに取得したバイトデータを連結 return(size.Concat(GetAllBytes(treeBits.ToArray(), haffmanCodes).ToArray()).ToArray()); }
private static void DebugHaffmanTree(HaffmanNode node, int indent, string bitCode) { if (indent == 0) { Console.WriteLine(); Console.WriteLine("DebugHaffmanTree"); } List <bool> bits = new List <bool>(); bits.AddRange(bits); int indent_ = indent > 0 ? indent - 1 : 0; string indentStr = new string(' ', 2 * indent_) + " --"; if (node.IsLeaf) { Console.WriteLine(indentStr + $"{node.Value.ToString("X2")}({bitCode})"); } else { Console.WriteLine(indentStr + $""); if (node.Left != null) { DebugHaffmanTree(node.Left, indent + 1, bitCode + "0"); } if (node.Right != null) { DebugHaffmanTree(node.Right, indent + 1, bitCode + "1"); } } }
/// <summary> /// 子ノードを持つ節を生成するためのコンストラクタ /// </summary> /// <param name="left">左子ノード</param> /// <param name="right">右子ノード</param> public HaffmanNode(HaffmanNode left, HaffmanNode right) { this.IsLeaf = false; // 節の出現頻度は、左右ノードの出現頻度の和 this.Frequency = left.Frequency + right.Frequency; // 左右のノード情報を代入 this.Left = left; this.Right = right; }
/// <summary> /// ハフマン解凍 /// </summary> /// <param name="data">解凍前データ</param> /// <returns>解凍後データ</returns> internal static byte[] Decode(byte[] data) { // 圧縮済みデータ IEnumerable <bool> bits = data.Skip(4).BytesToBits(); // 前4byte 圧縮前ファイルサイズ int size = BitConverter.ToInt32(data.Take(4).ToArray(), 0); // ハフマン木を再構成 HaffmanNode root = RegenerateHaffmanTree(ref bits); // debug if (debugMode) { DebugHaffmanTree(root, 0, ""); } // この時点でbitsデータから前方のハフマン木データが消えている // デコード処理本体 // 処理済みバイト数カウンタ int cnt = 0; HaffmanNode node = root; List <byte> output = new List <byte>(); foreach (bool bit in bits.ToArray()) { if (node.IsLeaf) { // 葉ノード到達 output.Add(node.Value); // 先頭に戻る node = root; // カウンタが圧縮前ファイルサイズと一致したら処理終了 cnt++; if (size <= cnt) { break; } } // ここはelse集約不可 if (!node.IsLeaf) { // 葉ノードでない if (bit) { // bitがtrueなら右の子ノードを探索 node = node.Right; } else { // bitがfalseなら左の子ノードを探索 node = node.Left; } } } return(output.ToArray()); }
public static void DebugString(string str) { byte[] bytes = Encoding.ASCII.GetBytes(str); int[] freq = GetFrequency(bytes); HaffmanNode root = GenerateHaffmanTree(freq); DebugHaffmanTree(root, 0, ""); Dictionary <byte, bool[]> table = GetHaffmanCodeTable(root); DebugHaffmanTable(table); }
/// <summary> /// ハフマンコード辞書を作成 /// </summary> /// <param name="node">ハフマン木ルートノード</param> /// <returns>ハフマンコード辞書</returns> private static Dictionary <byte, bool[]> GetHaffmanCodeTable(HaffmanNode node) { Dictionary <byte, bool[]> dictionaly = new Dictionary <byte, bool[]>(); for (int i = 0; i <= byte.MaxValue; i++) { byte target = (byte)i; dictionaly[target] = GenerateHaffmanCode(node, target); } return(dictionaly); }
/// <summary> /// ハフマン木生成 /// </summary> /// <param name="freq">頻度配列</param> /// <returns>ハフマン木ルートノード</returns> private static HaffmanNode GenerateHaffmanTree(int[] freq) { // ノードリスト List <HaffmanNode> nodeList = new List <HaffmanNode>(); for (int i = 0; i <= byte.MaxValue; i++) { // 頻度0であれば、ノード追加処理はいらないので次の値へ if (freq[i] == 0) { continue; } // ノードリスト最後尾にノード追加 nodeList.Add(new HaffmanNode((byte)i, freq[i])); } // ノードリストの中身がのこり1件になるまでループ while (1 < nodeList.Count) { // 先頭に頻度最小値が来るようにソート // 1ループで1つノードが増えるので、どうしても毎ループでソートが必要 nodeList = nodeList.OrderBy(e => e.Frequency).ToList(); // 出現頻度最小のノード⇒左子ノード HaffmanNode left = nodeList[0]; // 出現頻度が2番目に少ないノード⇒右子ノード HaffmanNode right = nodeList[1]; // 枝にしたノードをリストから消去 nodeList.RemoveAt(0); nodeList.RemoveAt(0); // 左右子ノードの親ノードを生成してリストに追加 nodeList.Add(new HaffmanNode(left, right)); } // キューに残っているノードがハフマン木のルート(最上部) HaffmanNode root = nodeList[0]; // 1種類のデータのみ出現する場合は、ダミーノードを用意 if (root.IsLeaf) { return new HaffmanNode() { IsLeaf = false, Left = root, Right = root } } ; // 左右子ノードはルート return(root); }
/// ハフマンコードを作成 /// </summary> /// <param name="node">ハフマン木ルートノード</param> /// <param name="target">コードを作成したいデータ</param> /// <param name="codeBits">現在のハフマンコード</param> /// <returns></returns> private static bool[] GenerateHaffmanCode(HaffmanNode node, byte target, List <bool> codeBits = null) { // 初回呼び出しならbitリスト初期化 if (codeBits == null) { codeBits = new List <bool>(); } // 末端のノード & 現在ノードと一致 if (node.IsLeaf && (node.Value == target)) { return(codeBits.ToArray()); } // 左側探索 if (node.Left != null) { // 左側なので0:falseをbitリストに追加 codeBits.Add(false); bool[] result = GenerateHaffmanCode(node.Left, target, codeBits); if (result != null) { return(result); } // 見つからないならbitリスト追加取り消し codeBits.RemoveAt(codeBits.Count - 1); } // 右側探索 if (node.Right != null) { // 右側なので1:trueをbitリストに追加 codeBits.Add(true); bool[] result = GenerateHaffmanCode(node.Right, target, codeBits); if (result != null) { return(result); } // 見つからないならbitリスト追加取り消し codeBits.RemoveAt(codeBits.Count - 1); } // 見つからないならnullを返す return(null); }
/// <summary> /// ハフマン木自体をビット化 /// </summary> /// <param name="root">ハフマン木ルートノード</param> /// <returns></returns> private static bool[] ConvertHaffmanTreeToBits(HaffmanNode root) { List <bool> bits = new List <bool>(); // スタックを使用して幅優先探索 Stack <HaffmanNode> stack = new Stack <HaffmanNode>(); stack.Push(root); // stackが空になる=ビット化完了まで繰り返し while (0 < stack.Count) { HaffmanNode node = stack.Pop(); if (node.IsLeaf) { // 葉ノードはtrue出力 bits.Add(true); // 実データを8bit出力 for (int i = 0; i < 8; i++) { bits.Add((node.Value >> 7 - i & 1) == 1); } } else { // 葉ノードでないならfalse出力 bits.Add(false); // 右左の順番で子ノードを積む if (node.Right != null) { stack.Push(node.Right); } if (node.Left != null) { stack.Push(node.Left); } } } return(bits.ToArray()); }