Pixie
Loading...
Searching...
No Matches
huffman_build_table.h
Go to the documentation of this file.
1#pragma once
2
14
15#include <algorithm>
16#include <array>
17#include <cstddef>
18#include <cstdint>
19#include <span>
20
21namespace pixie::detail {
22
23inline constexpr std::size_t kByteAlphabetSize = 256;
24inline constexpr std::size_t kMaximumHuffmanNodes = 2 * kByteAlphabetSize - 1;
25inline constexpr std::size_t kMaximumHuffmanCodeLength = 11;
26
28 std::int16_t symbol = -1;
29 std::int16_t left = -1;
30 std::int16_t right = -1;
31};
32
34 std::array<HuffmanBuildNode, kMaximumHuffmanNodes> tree{};
35 std::array<std::uint8_t, kMaximumHuffmanNodes> split_rank{};
36 std::array<std::uint8_t, kByteAlphabetSize> symbol_to_rank{};
37 std::int16_t root = -1;
38 std::size_t node_count = 0;
39 std::size_t symbol_count = 0;
40};
41
42namespace huffman_build_detail {
43
45 std::size_t frequency;
46 std::uint16_t symbol;
47};
48
49// Stable LSD radix sort. The input is seeded in symbol order, so equal
50// frequencies retain PivCo's (frequency, symbol) tie discipline.
51inline void sort_leaves_by_frequency(
52 std::span<FrequencyLeaf> leaves,
53 std::array<FrequencyLeaf, kByteAlphabetSize>& temporary) {
54 std::size_t maximum = 0;
55 for (const FrequencyLeaf leaf : leaves) {
56 maximum = std::max(maximum, leaf.frequency);
57 }
58 std::size_t byte_count = 0;
59 while (maximum != 0) {
60 ++byte_count;
61 maximum >>= 8;
62 }
63
64 FrequencyLeaf* source = leaves.data();
65 FrequencyLeaf* destination = temporary.data();
66 for (std::size_t byte = 0; byte < byte_count; ++byte) {
67 const std::size_t shift = byte * 8;
68 std::array<std::size_t, 256> counts{};
69 for (std::size_t index = 0; index < leaves.size(); ++index) {
70 ++counts[(source[index].frequency >> shift) & 0xff];
71 }
72 std::size_t prefix = 0;
73 for (std::size_t& count : counts) {
74 const std::size_t current = count;
75 count = prefix;
76 prefix += current;
77 }
78 for (std::size_t index = 0; index < leaves.size(); ++index) {
79 const std::size_t bucket = (source[index].frequency >> shift) & 0xff;
80 destination[counts[bucket]++] = source[index];
81 }
82 std::swap(source, destination);
83 }
84 if (source != leaves.data()) {
85 std::copy_n(source, leaves.size(), leaves.data());
86 }
87}
88
89inline std::array<std::uint8_t, kByteAlphabetSize> build_code_lengths(
90 std::span<const std::size_t> frequencies,
91 std::span<const std::uint16_t> used_symbols) {
92 std::array<std::uint8_t, kByteAlphabetSize> lengths{};
93 if (used_symbols.size() < 2) {
94 if (!used_symbols.empty()) {
95 lengths[used_symbols.front()] = 1;
96 }
97 return lengths;
98 }
99
100 std::array<FrequencyLeaf, kByteAlphabetSize> leaves{};
101 for (std::size_t index = 0; index < used_symbols.size(); ++index) {
102 const std::uint16_t symbol = used_symbols[index];
103 leaves[index] = {frequencies[symbol], symbol};
104 }
105 std::array<FrequencyLeaf, kByteAlphabetSize> sort_temporary{};
106 sort_leaves_by_frequency(std::span(leaves).first(used_symbols.size()),
107 sort_temporary);
108
109 std::array<std::size_t, kMaximumHuffmanNodes> node_frequency{};
110 std::array<std::uint16_t, kMaximumHuffmanNodes> parent{};
111 const std::size_t leaf_count = used_symbols.size();
112 for (std::size_t index = 0; index < leaf_count; ++index) {
113 node_frequency[index] = leaves[index].frequency;
114 }
115
116 std::size_t next_leaf = 0;
117 std::size_t internal_head = leaf_count;
118 std::size_t next_internal = leaf_count;
119 const auto take_minimum = [&]() {
120 if (next_leaf < leaf_count &&
121 (internal_head == next_internal ||
122 node_frequency[next_leaf] <= node_frequency[internal_head])) {
123 return next_leaf++;
124 }
125 return internal_head++;
126 };
127 for (std::size_t remaining = leaf_count; remaining > 1; --remaining) {
128 const std::size_t left = take_minimum();
129 const std::size_t right = take_minimum();
130 node_frequency[next_internal] =
131 node_frequency[left] + node_frequency[right];
132 parent[left] = static_cast<std::uint16_t>(next_internal);
133 parent[right] = static_cast<std::uint16_t>(next_internal);
134 ++next_internal;
135 }
136
137 const std::size_t root = next_internal - 1;
138 std::array<std::uint8_t, kMaximumHuffmanNodes> depth{};
139 for (std::size_t index = root; index-- > 0;) {
140 depth[index] = static_cast<std::uint8_t>(depth[parent[index]] + 1);
141 }
142 for (std::size_t index = 0; index < leaf_count; ++index) {
143 lengths[leaves[index].symbol] = depth[index];
144 }
145 return lengths;
146}
147
148// PivCo's DEFLATE-style limiter caps tree height so byte-rank construction has
149// a small, predictable recursion and scratch bound.
150inline void limit_code_lengths(
151 std::array<std::uint8_t, kByteAlphabetSize>& lengths) {
152 std::array<std::size_t, kByteAlphabetSize> length_counts{};
153 std::size_t maximum = 0;
154 for (const std::uint8_t length : lengths) {
155 if (length != 0) {
156 ++length_counts[length];
157 maximum = std::max(maximum, static_cast<std::size_t>(length));
158 }
159 }
160 if (maximum <= kMaximumHuffmanCodeLength) {
161 return;
162 }
163
164 for (std::size_t length = maximum; length > kMaximumHuffmanCodeLength;
165 --length) {
166 length_counts[kMaximumHuffmanCodeLength] += length_counts[length];
167 length_counts[length] = 0;
168 }
169
170 std::size_t kraft = 0;
171 for (std::size_t length = 1; length <= kMaximumHuffmanCodeLength; ++length) {
172 kraft += length_counts[length] << (kMaximumHuffmanCodeLength - length);
173 }
174 constexpr std::size_t kKraftTarget = std::size_t{1}
175 << kMaximumHuffmanCodeLength;
176 while (kraft > kKraftTarget) {
177 std::size_t best = kMaximumHuffmanCodeLength - 1;
178 while (best > 0 && length_counts[best] == 0) {
179 --best;
180 }
181 if (best == 0) {
182 break;
183 }
184 --length_counts[best];
185 ++length_counts[best + 1];
186 kraft -= std::size_t{1} << (kMaximumHuffmanCodeLength - best - 1);
187 }
188 while (kraft < kKraftTarget &&
189 length_counts[kMaximumHuffmanCodeLength] != 0) {
190 bool shortened = false;
191 for (std::size_t length = kMaximumHuffmanCodeLength - 1; length > 0;
192 --length) {
193 const std::size_t delta =
194 (std::size_t{1} << (kMaximumHuffmanCodeLength - length)) - 1;
195 if (kraft + delta <= kKraftTarget) {
196 --length_counts[kMaximumHuffmanCodeLength];
197 ++length_counts[length];
198 kraft += delta;
199 shortened = true;
200 break;
201 }
202 }
203 if (!shortened) {
204 break;
205 }
206 }
207
208 struct LengthSymbol {
209 std::uint8_t length;
210 std::uint8_t symbol;
211 };
212 std::array<LengthSymbol, kByteAlphabetSize> ordered{};
213 std::size_t count = 0;
214 for (std::size_t symbol = 0; symbol < lengths.size(); ++symbol) {
215 if (lengths[symbol] != 0) {
216 ordered[count++] = {
217 std::min<std::uint8_t>(lengths[symbol], kMaximumHuffmanCodeLength),
218 static_cast<std::uint8_t>(symbol)};
219 }
220 }
221 for (std::size_t index = 1; index < count; ++index) {
222 const LengthSymbol current = ordered[index];
223 std::size_t position = index;
224 while (position != 0 && ordered[position - 1].length > current.length) {
225 ordered[position] = ordered[position - 1];
226 --position;
227 }
228 ordered[position] = current;
229 }
230 std::size_t ordered_index = 0;
231 for (std::size_t length = 1; length <= kMaximumHuffmanCodeLength; ++length) {
232 for (std::size_t index = 0; index < length_counts[length]; ++index) {
233 lengths[ordered[ordered_index++].symbol] =
234 static_cast<std::uint8_t>(length);
235 }
236 }
237}
238
240 bool sibling_pair;
241 std::uint16_t depth;
242 std::uint16_t root_code;
243 std::size_t symbol_begin;
244};
245
246inline std::uint16_t assign_inorder_ranks(HuffmanBuildTable& table,
247 std::int16_t node_id,
248 std::uint16_t rank) {
249 const HuffmanBuildNode& node = table.tree[node_id];
250 if (node.symbol >= 0) {
251 table.symbol_to_rank[node.symbol] = static_cast<std::uint8_t>(rank);
252 return static_cast<std::uint16_t>(rank + 1);
253 }
254 rank = assign_inorder_ranks(table, node.left, rank);
255 table.split_rank[node_id] = static_cast<std::uint8_t>(rank - 1);
256 return assign_inorder_ranks(table, node.right, rank);
257}
258
259inline void build_canonical_tree(
260 const std::array<std::uint8_t, kByteAlphabetSize>& lengths,
261 HuffmanBuildTable& table) {
262 std::array<std::uint16_t, kMaximumHuffmanCodeLength + 1> length_counts{};
263 std::size_t maximum_length = 0;
264 for (const std::uint8_t length : lengths) {
265 if (length != 0) {
266 ++length_counts[length];
267 maximum_length =
268 std::max(maximum_length, static_cast<std::size_t>(length));
269 }
270 }
271
272 std::array<std::uint8_t, kByteAlphabetSize> ordered_symbols{};
273 std::array<std::size_t, kMaximumHuffmanCodeLength + 2> length_begin{};
274 std::array<std::size_t, kMaximumHuffmanCodeLength + 2> cursor{};
275 std::size_t accumulated = 0;
276 for (std::size_t length = 1; length <= maximum_length; ++length) {
277 length_begin[length] = accumulated;
278 cursor[length] = accumulated;
279 accumulated += length_counts[length];
280 }
281 length_begin[maximum_length + 1] = accumulated;
282 for (std::size_t symbol = 0; symbol < lengths.size(); ++symbol) {
283 const std::uint8_t length = lengths[symbol];
284 if (length != 0) {
285 ordered_symbols[cursor[length]++] = static_cast<std::uint8_t>(symbol);
286 }
287 }
288
289 std::array<CanonicalChunk, kByteAlphabetSize> chunks{};
290 std::size_t chunk_count = 0;
291 for (std::size_t length = 1; length <= maximum_length; ++length) {
292 std::size_t symbol = length_begin[length];
293 for (std::size_t pair = 0; pair < length_counts[length] / 2; ++pair) {
294 chunks[chunk_count++] = {true, static_cast<std::uint16_t>(length - 1), 0,
295 symbol};
296 symbol += 2;
297 }
298 if ((length_counts[length] & 1U) != 0) {
299 chunks[chunk_count++] = {false, static_cast<std::uint16_t>(length), 0,
300 symbol};
301 }
302 }
303 for (std::size_t index = 1; index < chunk_count; ++index) {
304 const CanonicalChunk current = chunks[index];
305 std::size_t position = index;
306 while (position != 0 && chunks[position - 1].depth > current.depth) {
307 chunks[position] = chunks[position - 1];
308 --position;
309 }
310 chunks[position] = current;
311 }
312
313 std::uint32_t code = 0;
314 std::size_t previous_depth = 0;
315 for (std::size_t index = 0; index < chunk_count; ++index) {
316 CanonicalChunk& chunk = chunks[index];
317 code <<= chunk.depth - previous_depth;
318 chunk.root_code = static_cast<std::uint16_t>(code);
319 ++code;
320 previous_depth = chunk.depth;
321 }
322
323 table.root = 0;
324 table.node_count = 1;
325 for (std::size_t index = 0; index < chunk_count; ++index) {
326 const CanonicalChunk& chunk = chunks[index];
327 std::int16_t node_id = table.root;
328 for (std::size_t bit = chunk.depth; bit-- > 0;) {
329 const bool right = ((chunk.root_code >> bit) & 1U) != 0;
330 std::int16_t& child =
331 right ? table.tree[node_id].right : table.tree[node_id].left;
332 if (child < 0) {
333 child = static_cast<std::int16_t>(table.node_count++);
334 }
335 node_id = child;
336 }
337 if (chunk.sibling_pair) {
338 HuffmanBuildNode& node = table.tree[node_id];
339 node.left = static_cast<std::int16_t>(table.node_count++);
340 table.tree[node.left].symbol = ordered_symbols[chunk.symbol_begin];
341 node.right = static_cast<std::int16_t>(table.node_count++);
342 table.tree[node.right].symbol = ordered_symbols[chunk.symbol_begin + 1];
343 } else {
344 table.tree[node_id].symbol = ordered_symbols[chunk.symbol_begin];
345 }
346 }
347
348 assign_inorder_ranks(table, table.root, 0);
349}
350
351} // namespace huffman_build_detail
352
359 std::span<const std::size_t> frequencies) {
360 HuffmanBuildTable table;
361 std::array<std::uint16_t, kByteAlphabetSize> used_symbols{};
362 for (std::size_t symbol = 0; symbol < frequencies.size(); ++symbol) {
363 if (frequencies[symbol] != 0) {
364 used_symbols[table.symbol_count++] = static_cast<std::uint16_t>(symbol);
365 }
366 }
367 if (table.symbol_count == 0) {
368 return table;
369 }
370 if (table.symbol_count == 1) {
371 table.root = 0;
372 table.node_count = 1;
373 table.tree[0].symbol = used_symbols[0];
374 table.symbol_to_rank[used_symbols[0]] = 0;
375 return table;
376 }
377
378 auto lengths = huffman_build_detail::build_code_lengths(
379 frequencies, std::span(used_symbols).first(table.symbol_count));
380 huffman_build_detail::limit_code_lengths(lengths);
381 huffman_build_detail::build_canonical_tree(lengths, table);
382 return table;
383}
384
385} // namespace pixie::detail
HuffmanBuildTable build_huffman_table(std::span< const std::size_t > frequencies)
Build PivCo's sibling-paired, non-flat byte Huffman tree.
Definition huffman_build_table.h:358
Definition huffman_build_table.h:27
Definition huffman_build_table.h:33
Definition huffman_build_table.h:239
Definition huffman_build_table.h:44