Skip to content

Commit 482b1fa

Browse files
june128leios
authored andcommitted
Add Huffman in C#. (#82)
* Add Huffman in C#. * Rename EncodeResult to EncodingResult. Remove lines left over from debugging. Add mention. * Replace children array with left and right child. Write the count of the readableBitString to console. Have Node implement IComparable<Node> and improve the code according to that. Improve the creation of the initial list of nodes. Refactor NodePriorityList. Improve Node creation. * Use a string instead of a List<bool>. Also add comments explaining why we're not using something like a BitArray. Change the name of the enumerator "boolean" to "bit". Fix the Pop method by using "this.nodes[0]" instead of "this.nodes.First()". Improve the way to find out, if a Node is a Leaf. * Make dictionary creation recursive. * Make HuffmanCoding non-static. Fix Add method. Change input to "bibbity bobbity". Remove unnecessary braces. * Use new mention template.
1 parent 430f3a1 commit 482b1fa

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// submitted by Julian Schacher (jspp), thanks to gustorn for the help
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
6+
namespace HuffmanCoding
7+
{
8+
public class EncodingResult
9+
{
10+
public string BitString { get; set; }
11+
public Dictionary<char, string> Dictionary { get; set; }
12+
public HuffmanCoding.Node Tree { get; set; }
13+
14+
public EncodingResult(string bitString, Dictionary<char, string> dictionary, HuffmanCoding.Node tree)
15+
{
16+
this.BitString = bitString;
17+
this.Dictionary = dictionary;
18+
this.Tree = tree;
19+
}
20+
}
21+
22+
public class HuffmanCoding
23+
{
24+
// The Node class used for the Huffman Tree.
25+
public class Node : IComparable<Node>
26+
{
27+
public Node LeftChild { get; set; }
28+
public Node RightChild { get; set; }
29+
public string BitString { get; set; } = "";
30+
public int Weight { get; set; }
31+
public string Key { get; set; }
32+
33+
public bool IsLeaf => LeftChild == null && RightChild == null;
34+
35+
// Creates a leaf. So just a node is created with the given values.
36+
public static Node CreateLeaf(char key, int weight) => new Node(key.ToString(), weight, null, null);
37+
// Creates a branch. Here a node is created by adding the keys and weights of both childs together.
38+
public static Node CreateBranch(Node leftChild, Node rightChild) => new Node(leftChild.Key + rightChild.Key, leftChild.Weight + rightChild.Weight, leftChild, rightChild);
39+
private Node(string key, int weight, Node leftChild, Node rightChild)
40+
{
41+
this.Key = key;
42+
this.Weight = weight;
43+
this.LeftChild = leftChild;
44+
this.RightChild = rightChild;
45+
}
46+
47+
public int CompareTo(Node other) => this.Weight - other.Weight;
48+
}
49+
50+
// Node with biggest value at the top.
51+
class NodePriorityList
52+
{
53+
public int Count => nodes.Count;
54+
55+
private List<Node> nodes = new List<Node>();
56+
57+
public NodePriorityList() { }
58+
public NodePriorityList(List<Node> givenNodes)
59+
{
60+
this.nodes = givenNodes.ToList();
61+
this.nodes.Sort();
62+
}
63+
64+
public void Add(Node newNode)
65+
{
66+
var index = this.nodes.BinarySearch(newNode);
67+
if (index < 0)
68+
this.nodes.Insert(~index, newNode);
69+
else
70+
this.nodes.Insert(index, newNode);
71+
}
72+
73+
public Node Pop()
74+
{
75+
var result = this.nodes[0];
76+
this.nodes.RemoveAt(0);
77+
return result;
78+
}
79+
}
80+
81+
public EncodingResult Encode(string input)
82+
{
83+
var root = CreateTree(input);
84+
var dictionary = CreateDictionary(root);
85+
var bitString = CreateBitString(input, dictionary);
86+
87+
return new EncodingResult(bitString, dictionary, root);
88+
}
89+
90+
public string Decode(EncodingResult result)
91+
{
92+
var output = "";
93+
Node currentNode = result.Tree;
94+
foreach (var bit in result.BitString)
95+
{
96+
// Go down the tree.
97+
if (bit == '0')
98+
currentNode = currentNode.LeftChild;
99+
else
100+
currentNode = currentNode.RightChild;
101+
102+
if (currentNode.IsLeaf)
103+
{
104+
output += currentNode.Key;
105+
currentNode = result.Tree;
106+
}
107+
}
108+
return output;
109+
}
110+
111+
private Node CreateTree(string input)
112+
{
113+
// Create a List of all characters and their count in input by putting them into nodes.
114+
var nodes = input
115+
.GroupBy(c => c)
116+
.Select(n => Node.CreateLeaf(n.Key, n.Count()))
117+
.ToList();
118+
119+
// Convert list of nodes to a NodePriorityList.
120+
var nodePriorityList = new NodePriorityList(nodes);
121+
122+
// Create Tree.
123+
while (nodePriorityList.Count > 1)
124+
{
125+
// Pop the two nodes with the smallest weights from the nodePriorityList and create a parentNode with the CreateBranch method. (This method adds the keys and weights of the childs together.)
126+
var leftChild = nodePriorityList.Pop();
127+
var rightChild = nodePriorityList.Pop();
128+
var parentNode = Node.CreateBranch(leftChild, rightChild);
129+
130+
nodePriorityList.Add(parentNode);
131+
}
132+
133+
return nodePriorityList.Pop();
134+
}
135+
136+
private Dictionary<char, string> CreateDictionary(Node root)
137+
{
138+
// We're using a string instead of a actual bits here, since it makes the code somewhat more readable and this is an educational example.
139+
var dictionary = new Dictionary<char, string>();
140+
CreateDictionary(root, "", dictionary);
141+
return dictionary;
142+
143+
void CreateDictionary(Node node, string bitString, Dictionary<char, string> localDictionary)
144+
{
145+
if (node.IsLeaf)
146+
localDictionary.Add(node.Key[0], bitString);
147+
else
148+
{
149+
if (node.LeftChild != null)
150+
CreateDictionary(node.LeftChild, bitString + '0', localDictionary);
151+
if (node.RightChild != null)
152+
CreateDictionary(node.RightChild, bitString + '1', localDictionary);
153+
}
154+
}
155+
}
156+
157+
158+
private string CreateBitString(string input, Dictionary<char, string> dictionary)
159+
{
160+
// We're using a string right here. While no compression is achieved with a string, it's the easiest way to display what the compressed result looks like. Also this is just an educational example.
161+
var bitString = "";
162+
foreach (var character in input)
163+
bitString += dictionary[character];
164+
165+
return bitString;
166+
}
167+
}
168+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// submitted by Julian Schacher (jspp), thanks to gustorn for the help
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace HuffmanCoding
6+
{
7+
class Program
8+
{
9+
static void Main(string[] args)
10+
{
11+
var huffmanCoding = new HuffmanCoding();
12+
13+
var result = huffmanCoding.Encode("bibbity bobbity");
14+
// The bitStrings are just strings and provide no compression. Look in HuffmanCoding.cs for explanation.
15+
// Print dictionary.
16+
foreach (var entry in result.Dictionary)
17+
System.Console.WriteLine($"{entry.Key} {entry.Value}");
18+
// Print BitString.
19+
System.Console.WriteLine($"{result.BitString} count: {result.BitString.Length}");
20+
21+
var originalString = huffmanCoding.Decode(result);
22+
System.Console.WriteLine(originalString);
23+
}
24+
}
25+
}

chapters/data_compression/huffman/huffman.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ Whether you use a stack or straight-up recursion also depends on the language, b
6666
{% sample lang="hs" %}
6767
### Haskell
6868
[import, lang:"haskell"](code/haskell/huffman.hs)
69+
{% sample lang="cs" %}
70+
### C# #
71+
HuffmanCoding.cs
72+
[import, lang:"csharp"](code/cs/HuffmanCoding.cs)
73+
Program.cs
74+
[import, lang:"csharp"](code/cs/Program.cs)
6975
{% sample lang="cpp" %}
7076
### C++
7177
[import, lang:"c_cpp"](code/c++/huffman.cpp)

0 commit comments

Comments
 (0)