-
Notifications
You must be signed in to change notification settings - Fork 29
Added huffman code #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<int> priorityQueue = new PriorityQueue<int>(); | ||
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)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace Algorithms.HuffmanCoding | ||
{ | ||
|
||
public class HuffmanNode : IComparable<HuffmanNode> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Public class should be placed in a separate file. |
||
{ | ||
public long frequency; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please follow the naming guideline There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also please rename "ch" to something more readable, like "character" or "symbol". |
||
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<HuffmanNode> priorityQueue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please specify accessibility level explicitly |
||
HuffmanNode root = null; | ||
Dictionary<char, string> prefixCode = new Dictionary<char, string>(); | ||
|
||
internal void BuildHuffmanTree(Dictionary<char, long> charFrequency) | ||
{ | ||
// Create a priority queue with capacity as dictionary size (no of unique chars) | ||
priorityQueue = new PriorityQueue<HuffmanNode>(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<char, long> GetCharacterFrequency(string inputStr) | ||
{ | ||
var charFrequency = new Dictionary<char, long>(); | ||
|
||
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; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Text; | ||
|
||
namespace Algorithms.HuffmanCoding | ||
{ | ||
public sealed class PriorityQueue<T> where T : IComparable<T> | ||
{ | ||
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<T>.Default.Equals(_items[i].Value, item)) | ||
{ | ||
RemoveAt(i); | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private struct IndexedItem : IComparable<IndexedItem> | ||
{ | ||
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; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case code for "a" should be 0, doesn't it?