-
Notifications
You must be signed in to change notification settings - Fork 1
/
HuffmanCoding.java
205 lines (194 loc) · 7.82 KB
/
HuffmanCoding.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HuffmanCoding {
// Element used to spilt between values in the table(header)
public static final String SPILT_ELEMENT = "`,";
public static final String TABLE_END_ELEMENT = SPILT_ELEMENT.repeat(2);
private String lastOperationHeader;
/**
* convert the input string to its comperessed form
*
* @param input input string ex.{"abc"}
* @return compression result ex.{"1000101101"}
*/
public String compress(String input) {
// creating a map of each character in the input and calculating its frequency
HashMap<Character, Integer> asciiMap = new HashMap<>();
for (Character c : input.toCharArray()) {
if (asciiMap.containsKey(c)) {
int frequency = asciiMap.get(c);
asciiMap.replace(c, frequency + 1);
} else {
asciiMap.put(c, 1);
}
}
// converting ascii map to intial huffman tree
ArrayList<HuffmanTreeNode> initalHuffmanTree = new ArrayList<>();
for (Map.Entry<Character, Integer> e : asciiMap.entrySet()) {
AsciiElement element = new AsciiElement(e.getKey().toString(), e.getValue());
initalHuffmanTree.add(new HuffmanTreeNode(element));
}
// build huffman tree using intial huffman tree
HuffmanTreeNode huffmanTreeRoot = buildHuffmanTree(initalHuffmanTree);
// create codes map form huffman tree
HashMap<Character, String> newCodesMap = generateCodesMap(asciiMap.keySet(), huffmanTreeRoot);
// create header string from codes map
String header = headerFromCodesMap(newCodesMap);
// intialize the last operation header global variable with the new header
setLastOperationHeader(header);
// write the input in its compressed form
StringBuilder output = new StringBuilder();
for (Character c : input.toCharArray()) {
output.append(newCodesMap.get(c));
}
return output.toString();
}
/**
* converts the codes map to header string
*
* @param codesMap hash map of each chracter as a key an its code as a value
* ex.{a:010,b:1,c:011}
* @return header string ex.{"a`,010`,b`,1`,c`,011`,`,"}
*/
private String headerFromCodesMap(HashMap<Character, String> codesMap) {
StringBuilder header = new StringBuilder();
for (Map.Entry<Character, String> e : codesMap.entrySet()) {
header.append(e.getKey()).append(SPILT_ELEMENT);
header.append(e.getValue()).append(SPILT_ELEMENT);
}
header.append(SPILT_ELEMENT);
return header.toString();
}
/**
* converts the codes map to header string
*
* @param header string ex.{"a`,010`,b`,1`,c`,011`,`,"}
* @return hash map of each chracter as a key an its code as a value
* ex.{a:010,b:1,c:011}
*/
private HashMap<Character, String> codesMapFromHeader(String header) {
String[] splitValues = header.split(SPILT_ELEMENT);
HashMap<Character, String> codesMap = new HashMap<>();
for (int i = 0; i < splitValues.length; i += 2) {
codesMap.put(splitValues[i].charAt(0), splitValues[i + 1]);
}
return codesMap;
}
/**
* create codes map from huffman tree by traversing it to find each character
* code
*
* @param characterSet set of characters to find thier codes
* @param huffmanTreeRoot the root node of huffman tree
* @return hash map of each chracter as a key an its code as a value
* ex.{a:010,b:1,c:011}
*/
private HashMap<Character, String> generateCodesMap(Set<Character> characterSet, HuffmanTreeNode huffmanTreeRoot) {
HashMap<Character, String> newCodesMap = new HashMap<>();
for (Character c : characterSet) {
if (characterSet.size() == 1) {
newCodesMap.put(c, "1");
break;
}
StringBuilder newCode = new StringBuilder();
HuffmanTreeNode node = huffmanTreeRoot;
while (!node.isLeaf()) {
if (node.getLeft().getData().getName().contains(c.toString())) {
newCode.append("0");
node = node.getLeft();
} else {
newCode.append("1");
node = node.getRight();
}
}
newCodesMap.put(c, newCode.toString());
}
return newCodesMap;
}
/**
* build huffman tree using huffman algorithm
*
* @param huffmanTree intial list of huffman tree nodes. each node's data =
* ex.{AsciiElement({'a',11})}, and it's left and right
* childs will be null
* @return new huffman tree root
*/
private HuffmanTreeNode buildHuffmanTree(ArrayList<HuffmanTreeNode> huffmanTree) {
// sorting the input to make sure that the first node is the smallest one
// and the second node is the second smallest one
sortHuffmanTree(huffmanTree);
// Stoping condition:
// if there is only one node this means that the algorithm is finished
// and this node is the root node so return it
if (huffmanTree.size() == 1) {
return huffmanTree.get(0);
}
// create a new node that will be the sum of the smallest two nodes
// remove the two smallest nodes from the tree
// add the new node to the tree
HuffmanTreeNode nodeSum = new HuffmanTreeNode();
nodeSum.createByTwoChilds(huffmanTree.get(0), huffmanTree.get(1));
huffmanTree.remove(0);
// the second smallest node took index 0 after removing the first node
huffmanTree.remove(0);
huffmanTree.add(nodeSum);
return buildHuffmanTree(huffmanTree);
}
/**
* sorts an arraylist of huffman tree node
*
* @param huffmanTree arraylist of huffman tree node
*/
private void sortHuffmanTree(ArrayList<HuffmanTreeNode> huffmanTree) {
Collections.sort(huffmanTree, new Comparator<HuffmanTreeNode>() {
@Override
public int compare(HuffmanTreeNode node_1, HuffmanTreeNode node_2) {
AsciiElement element_1 = node_1.getData();
AsciiElement element_2 = node_2.getData();
return Integer.compare(element_1.getFrequency(), element_2.getFrequency());
}
});
}
/**
* convert the comperessed string to its original form
*
* @param header a string that will be converted to codes map
* @param data comperessed string
* @return original string
*/
public String decompress(String header, String data) {
StringBuilder output = new StringBuilder();
HashMap<Character, String> codesMap = codesMapFromHeader(header);
StringBuilder codeBuilder = new StringBuilder();
for (int i = 0; i < data.length(); i++) {
codeBuilder.append(data.charAt(i));
for (Map.Entry<Character, String> e : codesMap.entrySet()) {
if (e.getValue().equals(codeBuilder.toString())) {
output.append(e.getKey());
codeBuilder.delete(0, codeBuilder.length());
}
}
}
return output.toString();
}
/**
* setter for lastOperationHeader
*
* @param lastOperationHeader
*/
public void setLastOperationHeader(String lastOperationHeader) {
this.lastOperationHeader = lastOperationHeader;
}
/**
* getter for lastOperationHeader
*
* @return
*/
public String getLastOperationHeader() {
return lastOperationHeader;
}
}