diff --git a/Algorithms/Algorithms.Tests/HuffmanCodingTests/HuffmanCodingTests.cs b/Algorithms/Algorithms.Tests/HuffmanCodingTests/HuffmanCodingTests.cs new file mode 100644 index 0000000..888476c --- /dev/null +++ b/Algorithms/Algorithms.Tests/HuffmanCodingTests/HuffmanCodingTests.cs @@ -0,0 +1,33 @@ +using Algorithms.HuffmanCoding; +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Algorithms.Tests.HuffmanCodingTests +{ + public class HuffmanCodingTests + { + [Fact] + public void TestPriorityQueue() + { + PriorityQueue priorityQueue = new PriorityQueue(); + priorityQueue.Enqueue(10); + priorityQueue.Enqueue(20); + priorityQueue.Enqueue(5); + priorityQueue.Enqueue(100); + + Assert.Equal(5, priorityQueue.Dequeue()); + } + + [Theory] + [InlineData("abaacaabd", "d", "011")] + [InlineData("aaaaaaaaaaaaaaaaaaaaaaaaaaac", "a", "1")] + [InlineData("cad", "acd", "11100")] + public void TestHuffmanCode(string data, string input, string expectedCode) + { + HuffmanCoding.HuffmanCoding huffmanCoding = new HuffmanCoding.HuffmanCoding(); + Assert.Equal(expectedCode, huffmanCoding.GetHuffmanCode(data, input)); + } + } +} diff --git a/Algorithms/Algorithms/HuffmanCoding/HuffmanCoding.cs b/Algorithms/Algorithms/HuffmanCoding/HuffmanCoding.cs new file mode 100644 index 0000000..d284d63 --- /dev/null +++ b/Algorithms/Algorithms/HuffmanCoding/HuffmanCoding.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Algorithms.HuffmanCoding +{ + + public class HuffmanNode : IComparable + { + public long frequency; + public char ch; + + public HuffmanNode left; + public HuffmanNode right; + + public HuffmanNode(char ch, long frequency) + { + this.ch = ch; + this.frequency = frequency; + } + + public int CompareTo(HuffmanNode node) + { + return frequency.CompareTo(node.frequency); + } + } + + public class HuffmanCoding + { + PriorityQueue priorityQueue; + HuffmanNode root = null; + Dictionary prefixCode = new Dictionary(); + + internal void BuildHuffmanTree(Dictionary charFrequency) + { + // Create a priority queue with capacity as dictionary size (no of unique chars) + priorityQueue = new PriorityQueue(charFrequency.Count); + + foreach (var item in charFrequency) + { + var node = new HuffmanNode(item.Key, item.Value); + priorityQueue.Enqueue(node); + } + + while (priorityQueue.Count > 1) + { + var first = priorityQueue.Dequeue(); + var second = priorityQueue.Dequeue(); + + var parent = new HuffmanNode('$', first.frequency + second.frequency); + parent.left = first; + parent.right = second; + + root = parent; + + priorityQueue.Enqueue(parent); + } + } + + internal void GeneratePrefixCode(HuffmanNode node, string input) + { + if (node.left == null && node.right == null && Char.IsLetter(node.ch)) + { + prefixCode.Add(node.ch, input); + return; + } + + GeneratePrefixCode(node.left, input + "0"); + GeneratePrefixCode(node.right, input + "1"); + } + + internal Dictionary GetCharacterFrequency(string inputStr) + { + var charFrequency = new Dictionary(); + + foreach (var ch in inputStr) + { + if (charFrequency.ContainsKey(ch)) + charFrequency[ch]++; + else + charFrequency[ch] = 1; + } + return charFrequency; + } + + public string GetHuffmanCode(string data, string input) + { + if (string.IsNullOrWhiteSpace(data)) + throw new ArgumentNullException("Invalid data"); + if (string.IsNullOrWhiteSpace(input)) + throw new ArgumentNullException("Invalid input string"); + + string huffmanCode = string.Empty; + + var charFrequency = GetCharacterFrequency(data); + BuildHuffmanTree(charFrequency); + GeneratePrefixCode(root, string.Empty); + + if(prefixCode?.Count > 0) + { + foreach(var ch in input) + { + huffmanCode += prefixCode[ch]; + } + } + return huffmanCode; + } + } +} diff --git a/Algorithms/Algorithms/HuffmanCoding/PriorityQueue.cs b/Algorithms/Algorithms/HuffmanCoding/PriorityQueue.cs new file mode 100644 index 0000000..59ead12 --- /dev/null +++ b/Algorithms/Algorithms/HuffmanCoding/PriorityQueue.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Algorithms.HuffmanCoding +{ + public sealed class PriorityQueue where T : IComparable + { + private long _count = long.MinValue; + private IndexedItem[] _items; + private int _size; + + public PriorityQueue() + : this(16) + { + } + + public PriorityQueue(int capacity) + { + _items = new IndexedItem[capacity]; + _size = 0; + } + + private bool IsHigherPriority(int left, int right) + { + return _items[left].CompareTo(_items[right]) < 0; + } + + private int Percolate(int index) + { + if (index >= _size || index < 0) + { + return index; + } + + var parent = (index - 1) / 2; + while (parent >= 0 && parent != index && IsHigherPriority(index, parent)) + { + // swap index and parent + var temp = _items[index]; + _items[index] = _items[parent]; + _items[parent] = temp; + + index = parent; + parent = (index - 1) / 2; + } + + return index; + } + + private void Heapify(int index) + { + if (index >= _size || index < 0) + { + return; + } + + while (true) + { + var left = 2 * index + 1; + var right = 2 * index + 2; + var first = index; + + if (left < _size && IsHigherPriority(left, first)) + { + first = left; + } + + if (right < _size && IsHigherPriority(right, first)) + { + first = right; + } + + if (first == index) + { + break; + } + + // swap index and first + var temp = _items[index]; + _items[index] = _items[first]; + _items[first] = temp; + + index = first; + } + } + + public int Count => _size; + + public T Peek() + { + if (_size == 0) + { + throw new InvalidOperationException("Empty Heap"); + } + + return _items[0].Value; + } + + private void RemoveAt(int index) + { + _items[index] = _items[--_size]; + _items[_size] = default; + + if (Percolate(index) == index) + { + Heapify(index); + } + + if (_size < _items.Length / 4) + { + var temp = _items; + _items = new IndexedItem[_items.Length / 2]; + Array.Copy(temp, 0, _items, 0, _size); + } + } + + public T Dequeue() + { + var result = Peek(); + RemoveAt(0); + return result; + } + + public void Enqueue(T item) + { + if (_size >= _items.Length) + { + var temp = _items; + _items = new IndexedItem[_items.Length * 2]; + Array.Copy(temp, _items, temp.Length); + } + + var index = _size++; + _items[index] = new IndexedItem { Value = item, Id = ++_count }; + Percolate(index); + } + + public bool Remove(T item) + { + for (var i = 0; i < _size; ++i) + { + if (EqualityComparer.Default.Equals(_items[i].Value, item)) + { + RemoveAt(i); + return true; + } + } + + return false; + } + + private struct IndexedItem : IComparable + { + public T Value; + public long Id; + + public int CompareTo(IndexedItem other) + { + var c = Value.CompareTo(other.Value); + if (c == 0) + { + c = Id.CompareTo(other.Id); + } + + return c; + } + } + } +}