Repository navigation
Expand file tree
/
Copy pathHuffmanTree.java
More file actions
259 lines (209 loc) · 9.31 KB
/
Copy pathHuffmanTree.java
File metadata and controls
259 lines (209 loc) · 9.31 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
import java.util.*;
enum Direction {
ENCODING,
DECODING
}
class HuffmanTree {
private final Direction direction;
private Node rootNode;
private ArrayList<Node> huffmanTree;
private ArrayList<Node> nodesInTree;
private HashSet<Character> charactersInTree; // Used for quickly checking if a character is in the tree
private char currentChar;
private String currentCharCode;
private Node currentNode;
private boolean characterMatchFound;
private boolean gotDag;
private boolean gotEof;
HuffmanTree(Direction direction) {
this.direction = direction;
this.charactersInTree = new HashSet<>();
this.currentChar = '\0';
this.currentCharCode = "";
this.characterMatchFound = false;
this.gotDag = false;
this.gotEof = false;
this.huffmanTree = new ArrayList<>();
createBaseStructure();
}
private void createBaseStructure() {
// Set up the original structure with a dagger node and an end-of-file node
Node DAG = new Node(NodeType.DAG);
DAG.setValue(0);
Node EOF = new Node(NodeType.EOF);
EOF.setValue(0);
this.rootNode = new Node(NodeType.COMBINATION);
this.rootNode.setValue(0);
this.rootNode.setLeftChild(DAG);
this.rootNode.setRightChild(EOF);
this.currentNode = this.rootNode;
this.huffmanTree.add(this.rootNode);
}
String addCharAndGetCode(int incomingCharAsInt) {
String codeToWrite = "";
char characterToAdd = (char) incomingCharAsInt;
// Check if character is already in the structure
if (isCharacterInTree(characterToAdd)) {
// Get the code for the character
codeToWrite += getCharacterCode(characterToAdd, true);
// Kl is my class to print everything - only used for printing the progress of the algorithm
if (this.direction == Direction.ENCODING) Kl.p(codeToWrite);
} else {
// Character not already in tree so need to add it and get dagger code
codeToWrite += getNodeTypeCode(NodeType.DAG);
// Get binary representation of ascii character
String charAsBinary = String.format("%8s", Integer.toBinaryString(incomingCharAsInt)).replace(' ', '0');
// Printing the progress of the algorithm
if (this.direction == Direction.ENCODING) Kl.p("DAG(" + codeToWrite + ") ASCII(" + charAsBinary + ")");
// Add the binary representation of the ascii character to the string
codeToWrite += charAsBinary;
addCharToTree(characterToAdd);
}
return codeToWrite;
}
private boolean isCharacterInTree(char c) {
return this.charactersInTree.contains(c);
}
// Used to get the codeword for the character
private String getCharacterCode(char c, boolean increaseFreq) {
this.currentCharCode = "";
getNodeCode(this.rootNode, c, NodeType.SINGLETON, increaseFreq, "", false);
return this.currentCharCode;
}
// Used to get the codeword for a special node
private String getNodeTypeCode(NodeType type) {
this.currentCharCode = "";
getNodeCode(this.rootNode, '\0', type, false, "", false);
return this.currentCharCode;
}
// Method used to get any node codeword
private void getNodeCode(Node node, char c, NodeType type, boolean increaseFreq, String code, boolean toList) {
// Using a DFS as generally quicker to reach all leaf nodes
if (node.getLeftChild() != null)
getNodeCode(node.getLeftChild(), c, type, increaseFreq, code + "0", toList);
if (node.getRightChild() != null)
getNodeCode(node.getRightChild(), c, type, increaseFreq, code + "1", toList);
// Double check we are at a leaf node
if (node.getLeftChild() == null && node.getRightChild() == null) {
if (toList) this.nodesInTree.add(node); // Add all leaf nodes to an ArrayList, not discriminating against type
if (node.getType() == type && node.getCharacter() == c) {
if (increaseFreq && node.getType() == NodeType.SINGLETON) node.setValue(node.getValue()+1);
this.currentCharCode = code;
}
}
}
private void addCharToTree(char c) {
// Insert new node for character after last item in tree
Node newCharNode = new Node(NodeType.SINGLETON);
newCharNode.setValue(1);
newCharNode.setCharacter(c);
this.charactersInTree.add(c);
rebuildHuffmanTree(newCharNode);
}
private void rebuildHuffmanTree(Node nodeToAdd) {
// Get list of all singleton nodes
this.nodesInTree = new ArrayList<>();
getNodeCode(this.rootNode, '\0', null, false, "", true);
// Add a new node if required
if (nodeToAdd != null)
this.nodesInTree.add(nodeToAdd);
// I could use a PriorityQueue (which is a heap) for this however it is on average slower than quicksorting a list which is what the default sort() method uses
this.nodesInTree = sortNodeList(this.nodesInTree);
// Loop over the list of nodes as long as it contains more than 1 node
while (this.nodesInTree.size() > 1) {
// Get the 2 nodes with lowest frequencies
Node newLeftChild = this.nodesInTree.get(this.nodesInTree.size()-1);
Node newRightChild = this.nodesInTree.get(this.nodesInTree.size()-2);
this.nodesInTree.remove(this.nodesInTree.size()-1);
this.nodesInTree.remove(this.nodesInTree.size()-1);
// Create the new parent node
Node newComboNode = new Node(NodeType.COMBINATION);
newComboNode.setValue(newLeftChild.getValue()+newRightChild.getValue());
newComboNode.setLeftChild(newLeftChild);
newComboNode.setRightChild(newRightChild);
// Add the node back into the list and sortr
this.nodesInTree.add(newComboNode);
this.nodesInTree = sortNodeList(this.nodesInTree);
}
this.rootNode = this.nodesInTree.get(0);
// If decoding, reset the current node pointer to the root node
if (this.direction == Direction.DECODING) {
this.currentNode = this.rootNode;
this.gotDag = false;
}
}
// Sort the list of nodes in descending order on the node frequency
private ArrayList<Node> sortNodeList(ArrayList<Node> nodes) {
nodes.sort(Comparator.comparing(Node::getValue).reversed());
return nodes;
}
// How to print a tree
// https://stackoverflow.com/a/8948691/3259361
void doPrintTree(String prefix, Node node, boolean isTail, String code) {
// Used to print the line of the tree
Kl.pl(prefix + (isTail ? "└── " : "├── ") + getLabel(node, code));
// Not at a leaf node - go deeper
if (node.getLeftChild() != null)
doPrintTree(prefix + (isTail ? " " : "│ "), node.getLeftChild(), false, code + "0");
// Not at a leaf node - go deeper
if (node.getRightChild() != null)
doPrintTree(prefix + (isTail ? " " : "│ "), node.getRightChild(), true, code + "1");
}
// Used for printing the progress of the algorithm
private String getLabel(Node node, String code) {
StringBuilder label = new StringBuilder();
if (node.type == NodeType.DAG)
label.append("DAG(code:").append(code).append(")");
else if (node.type == NodeType.EOF)
label.append("EOF(code:").append(code).append(")");
else if (node.type == NodeType.COMBINATION)
label.append("COMBINATION(frequency:").append(node.getValue()).append(")");
else if (node.type == NodeType.SINGLETON)
label.append("\"").append(node.getCharacter()).append("\":(code:").append(code).append(", frequency:").append(node.getValue()).append(")");
return label.toString();
}
// Print the tree
void printTree() {
Kl.pl("");
doPrintTree("", this.rootNode, true, "");
Kl.pl("");
}
boolean isCharacterMatchFound() {
return this.characterMatchFound;
}
char getCurrentCharacter() {
this.characterMatchFound = false;
this.currentNode = this.rootNode;
return this.currentChar;
}
boolean didGetDag() {
return this.gotDag;
}
boolean didGetEof() {
return this.gotEof;
}
String getEofCode() {
return getNodeTypeCode(NodeType.EOF);
}
// For decoding purposes
void takeNextBit(int nextBit) {
Kl.p(nextBit + "");
// Traversing the tree
if (nextBit == 0)
this.currentNode = this.currentNode.getLeftChild();
else if (nextBit == 1)
this.currentNode = this.currentNode.getRightChild();
// Getting the information
if (this.currentNode.getType() == NodeType.SINGLETON) {
this.characterMatchFound = true;
this.currentChar = this.currentNode.getCharacter();
Kl.p("(\"" + this.currentChar+"\")");
} else if (this.currentNode.getType() == NodeType.DAG) {
this.gotDag = true;
Kl.p("(DAG) ");
} else if (this.currentNode.getType() == NodeType.EOF) {
this.gotEof = true;
Kl.pl(getEofCode()+"(EOF)");
}
}
}