-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimpleFrequencyTable.java
More file actions
204 lines (167 loc) · 6.9 KB
/
Copy pathSimpleFrequencyTable.java
File metadata and controls
204 lines (167 loc) · 6.9 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
package nayuki.arithcode;
/**
* A mutable table of symbol frequencies. The number of symbols cannot be changed after construction.
* The current algorithm for calculating cumulative frequencies takes linear time, but there exist faster algorithms.
*/
public final class SimpleFrequencyTable implements FrequencyTable {
// The frequency for each symbol.
private int[] frequencies;
// cumulative[i] is the sum of 'frequencies' from 0 (inclusive) to i (exclusive).
// Initialized lazily. When this is not null, the data is valid.
private int[] cumulative;
// Equal to the sum of 'frequencies'.
private int total;
/**
* Creates a frequency table from the specified array of symbol frequencies. There must be at least 1 symbol, and no symbol has a negative frequency.
* @param freqs the array of symbol frequencies
* @throws NullPointerException if {@code freqs} is {@code null}
* @throws IllegalArgumentException if {@code freqs.length} < 1 or any element {@code freqs[i]} < 0
* @throws ArithmeticException if the total of {@code freqs} exceeds {@code Integer.MAX_VALUE}
*/
public SimpleFrequencyTable(int[] freqs) {
if (freqs == null)
throw new NullPointerException("Argument is null");
if (freqs.length == 0)
throw new IllegalArgumentException("At least 1 symbol needed");
frequencies = freqs.clone(); // Defensive copy
total = 0;
for (int x : frequencies) {
if (x < 0)
throw new IllegalArgumentException("Negative frequency");
total = checkedAdd(x, total);
}
cumulative = null;
}
/**
* Creates a frequency table by copying the specified frequency table.
* @param freqTab the frequency table to copy
* @throws NullPointerException if {@code freqTab} is {@code null}
* @throws IllegalArgumentException if {@code freqTab.getSymbolLimit()} < 1 or any element {@code freqTab.get(i)} < 0
* @throws ArithmeticException if the total of all {@code freqTab} elements exceeds {@code Integer.MAX_VALUE}
*/
public SimpleFrequencyTable(FrequencyTable freqTab) {
if (freqTab == null)
throw new NullPointerException("Argument is null");
int numSym = freqTab.getSymbolLimit();
if (numSym < 0)
throw new IllegalArgumentException("At least 1 symbol needed");
frequencies = new int[numSym];
total = 0;
for (int i = 0; i < frequencies.length; i++) {
int x = freqTab.get(i);
if (x < 0)
throw new IllegalArgumentException("Negative frequency");
frequencies[i] = x;
total = checkedAdd(x, total);
}
cumulative = null;
}
/**
* Returns the number of symbols in this frequency table.
* @return the number of symbols in this frequency table
*/
public int getSymbolLimit() {
return frequencies.length;
}
/**
* Returns the frequency of the specified symbol. The returned value is at least 0.
* @param symbol the symbol to query
* @return the frequency of the specified symbol
* @throws IllegalArgumentException if {@code symbol} < 0 or {@code symbol} ≥ {@code getSymbolLimit()}
*/
public int get(int symbol) {
if (symbol < 0 || symbol >= frequencies.length)
throw new IllegalArgumentException("Symbol out of range");
return frequencies[symbol];
}
/**
* Sets the frequency of the specified symbol to the specified value. The frequency value must be at least 0.
* @param symbol the symbol to set
* @param freq the frequency value to set
* @throws IllegalArgumentException if {@code symbol} < 0 or {@code symbol} ≥ {@code getSymbolLimit()}
*/
public void set(int symbol, int freq) {
if (symbol < 0 || symbol >= frequencies.length)
throw new IllegalArgumentException("Symbol out of range");
if (freq < 0)
throw new IllegalArgumentException("Negative frequency");
total = checkedAdd(total - frequencies[symbol], freq);
frequencies[symbol] = freq;
cumulative = null;
}
/**
* Increments the frequency of the specified symbol. An exception should be thrown if the symbol is out of range.
* @param symbol the symbol whose frequency to increment
* @throws IllegalArgumentException if {@code symbol} < 0 or {@code symbol} ≥ {@code getSymbolLimit()}
*/
public void increment(int symbol) {
if (symbol < 0 || symbol >= frequencies.length)
throw new IllegalArgumentException("Symbol out of range");
if (frequencies[symbol] == Integer.MAX_VALUE)
throw new RuntimeException("Arithmetic overflow");
total = checkedAdd(total, 1);
frequencies[symbol]++;
cumulative = null;
}
/**
* Returns the total of all symbol frequencies. The returned value is at least 0 and is always equal to {@code getHigh(getSymbolLimit() - 1)}.
* @return the total of all symbol frequencies
*/
public int getTotal() {
return total;
}
/**
* Returns the total of the frequencies of all the symbols below the specified one. The returned value is at least 0. An exception should be thrown if the symbol is out of range.
* @param symbol the symbol to query
* @return the total of the frequencies of all the symbols below {@code symbol}
* @throws IllegalArgumentException if {@code symbol} < 0 or {@code symbol} ≥ {@code getSymbolLimit()}
*/
public int getLow(int symbol) {
if (symbol < 0 || symbol >= frequencies.length)
throw new IllegalArgumentException("Symbol out of range");
if (cumulative == null)
initCumulative();
return cumulative[symbol];
}
/**
* Returns the total of the frequencies of the specified symbol and all the ones below. The returned value is at least 0. An exception should be thrown if the symbol is out of range.
* @param symbol the symbol to query
* @return the total of the frequencies of {@code symbol} and all the ones below
* @throws IllegalArgumentException if {@code symbol} < 0 or {@code symbol} ≥ {@code getSymbolLimit()}
*/
public int getHigh(int symbol) {
if (symbol < 0 || symbol >= frequencies.length)
throw new IllegalArgumentException("Symbol out of range");
if (cumulative == null)
initCumulative();
return cumulative[symbol + 1];
}
private void initCumulative() {
cumulative = new int[frequencies.length + 1];
int sum = 0;
for (int i = 0; i < frequencies.length; i++) {
sum = checkedAdd(frequencies[i], sum);
cumulative[i + 1] = sum;
}
if (sum != total)
throw new AssertionError();
}
/**
* Returns a string representation of this frequency table. The current format shows all the symbols and frequencies. The format is subject to change.
* @return a string representation of this frequency table
*/
public String toString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < frequencies.length; i++)
sb.append(String.format("%d\t%d%n", i, frequencies[i]));
return sb.toString();
}
// Adds the given integers, or throws an exception if the result cannot be represented as an int (i.e. overflow).
private static int checkedAdd(int x, int y) {
int z = x + y;
if (y > 0 && z < x || y < 0 && z > x)
throw new ArithmeticException("Arithmetic overflow");
else
return z;
}
}