Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
850256856c |
@ -28,6 +28,7 @@ target_compile_features(consteval_huffman INTERFACE cxx_std_20)
|
||||
|
||||
# ---- Install ----
|
||||
|
||||
include(CPack)
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
@ -60,7 +61,3 @@ install(EXPORT consteval_huffmanTargets
|
||||
NAMESPACE tcsullivan::
|
||||
DESTINATION "${consteval_huffman_install_cmakedir}"
|
||||
COMPONENT consteval_huffman_Package)
|
||||
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
include(CPack)
|
||||
endif()
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define TCSULLIVAN_CONSTEVAL_HUFFMAN_HPP_
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
@ -17,15 +18,13 @@ namespace detail
|
||||
// Provides a string container for the huffman compressor.
|
||||
// Using this allows for automatic string data length measurement, as
|
||||
// well as implementation of the _huffman suffix.
|
||||
template<typename T, unsigned long int N>
|
||||
requires(std::same_as<std::remove_cvref_t<T>, char> ||
|
||||
std::same_as<std::remove_cvref_t<T>, unsigned char>)
|
||||
template<unsigned long int N>
|
||||
struct huffman_string_container {
|
||||
T data[N];
|
||||
consteval huffman_string_container(const T (&s)[N]) noexcept {
|
||||
char data[N];
|
||||
consteval huffman_string_container(const char (&s)[N]) noexcept {
|
||||
std::copy(s, s + N, data);
|
||||
}
|
||||
consteval operator const T *() const noexcept {
|
||||
consteval operator const char *() const noexcept {
|
||||
return data;
|
||||
}
|
||||
consteval auto size() const noexcept {
|
||||
@ -37,13 +36,12 @@ namespace detail
|
||||
/**
|
||||
* Compresses the given data string using Huffman coding, providing a
|
||||
* minimal run-time interface for decompressing the data.
|
||||
* @tparam raw_data The string of data to be compressed.
|
||||
* @tparam data The string of data to be compressed.
|
||||
*/
|
||||
template<auto raw_data>
|
||||
requires(
|
||||
std::same_as<std::remove_cvref_t<decltype(raw_data)>,
|
||||
detail::huffman_string_container<std::remove_cvref_t<decltype(raw_data.data[0])>,
|
||||
raw_data.size()>> &&
|
||||
detail::huffman_string_container<raw_data.size()>> &&
|
||||
raw_data.size() > 0)
|
||||
class huffman_compressor
|
||||
{
|
||||
@ -69,38 +67,52 @@ private:
|
||||
* This list is sorted by increasing frequency.
|
||||
* @return Compile-time allocated array of nodes
|
||||
*/
|
||||
consteval static auto build_node_list() noexcept {
|
||||
// Build a list for counting every occuring value
|
||||
auto list = std::span(new node[256] {}, 256);
|
||||
for (int i = 0; i < 256; i++)
|
||||
list[i].value = i;
|
||||
for (usize_t i = 0; i < raw_data.size(); i++)
|
||||
list[raw_data[i]].freq++;
|
||||
struct node_list_t {
|
||||
node list[256] = {};
|
||||
usize_t fit_size = 0;
|
||||
|
||||
std::sort(list.begin(), list.end(),
|
||||
[](const auto& a, const auto& b) { return a.freq < b.freq; });
|
||||
consteval node_list_t() noexcept {
|
||||
// Build a list for counting every occuring value
|
||||
for (int i = 0; i < 256; i++)
|
||||
list[i].value = i;
|
||||
for (usize_t i = 0; i < raw_data.size(); i++)
|
||||
list[raw_data[i]].freq++;
|
||||
|
||||
// Filter out the non-occuring values, and build a compact list to return
|
||||
auto first_valid_node = std::find_if(list.begin(), list.end(),
|
||||
[](const auto& n) { return n.freq != 0; });
|
||||
auto fit_size = std::distance(first_valid_node, list.end());
|
||||
if (fit_size < 2)
|
||||
fit_size = 2;
|
||||
auto fit_list = std::span(new node[fit_size] {}, fit_size);
|
||||
std::copy(first_valid_node, list.end(), fit_list.begin());
|
||||
delete[] list.data();
|
||||
return fit_list;
|
||||
}
|
||||
std::sort(list, list + 256,
|
||||
[](const auto& a, const auto& b) { return a.freq < b.freq; });
|
||||
|
||||
// Filter out the non-occuring values, and build a compact list to return
|
||||
auto first_valid_node = std::find_if(list, list + 256,
|
||||
[](const auto& n) { return n.freq != 0; });
|
||||
fit_size = std::distance(first_valid_node, list + 256);
|
||||
if (fit_size < 2)
|
||||
fit_size = 2;
|
||||
//auto fit_list = std::span(new node[fit_size] {}, fit_size);
|
||||
std::copy(first_valid_node, list + 256, list);
|
||||
}
|
||||
consteval node_list_t(const node_list_t& other) noexcept {
|
||||
fit_size = other.fit_size;
|
||||
for (int i = 0; i < size(); i++)
|
||||
list[i] = other.list[i];
|
||||
}
|
||||
|
||||
consteval auto size() const noexcept { return fit_size; }
|
||||
consteval auto data() noexcept { return list; }
|
||||
consteval auto begin() noexcept { return list; }
|
||||
consteval auto end() noexcept { return list + fit_size; }
|
||||
consteval auto& operator[](usize_t i) noexcept { return list[i]; }
|
||||
consteval auto& front() noexcept { return *list; }
|
||||
};
|
||||
constexpr static auto node_list = node_list_t();
|
||||
|
||||
/**
|
||||
* Returns the count of how many nodes are in the node tree.
|
||||
*/
|
||||
consteval static auto tree_count() noexcept {
|
||||
auto list = build_node_list();
|
||||
auto count = list.size() * 2 - 1;
|
||||
delete[] list.data();
|
||||
consteval static auto get_tree_count() noexcept {
|
||||
auto count = node_list.size() * 2 - 1;
|
||||
return count;
|
||||
}
|
||||
constexpr static auto tree_count = get_tree_count();
|
||||
|
||||
/**
|
||||
* Builds a tree out of the node list, allowing for the calculation of
|
||||
@ -108,8 +120,8 @@ private:
|
||||
* @return Compile-time allocated tree of nodes, root node at index zero.
|
||||
*/
|
||||
consteval static auto build_node_tree() noexcept {
|
||||
auto list = build_node_list();
|
||||
auto tree = std::span(new node[tree_count()] {}, tree_count());
|
||||
auto list = node_list_t(node_list);
|
||||
auto tree = std::span(new node[tree_count] {}, tree_count);
|
||||
|
||||
auto list_end = list.end(); // Track end of list as it shrinks
|
||||
auto tree_begin = tree.end(); // Build tree from bottom
|
||||
@ -157,7 +169,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
delete[] list.data();
|
||||
return tree;
|
||||
}
|
||||
|
||||
@ -165,14 +176,13 @@ private:
|
||||
* Determines the size of the compressed data.
|
||||
* @return A pair of total bytes used, and bits used in last byte.
|
||||
*/
|
||||
consteval static auto compressed_size_info() noexcept {
|
||||
consteval static auto get_compressed_size_info() noexcept {
|
||||
auto tree = build_node_tree();
|
||||
size_t bytes = 1, bits = 0;
|
||||
|
||||
for (usize_t i = 0; i < raw_data.size(); i++) {
|
||||
auto c = static_cast<int>(raw_data[i]);
|
||||
auto leaf = std::find_if(tree.begin(), tree.end(),
|
||||
[c](const auto& n) { return n.value == c; });
|
||||
[c = raw_data[i]](const auto& n) { return n.value == c; });
|
||||
|
||||
while (leaf->parent != -1) {
|
||||
if (++bits == 8)
|
||||
@ -184,6 +194,7 @@ private:
|
||||
delete[] tree.data();
|
||||
return std::make_pair(bytes, bits);
|
||||
}
|
||||
constexpr static auto compressed_size_info = get_compressed_size_info();
|
||||
|
||||
/**
|
||||
* Compresses the input data, storing the result in the object instance.
|
||||
@ -192,7 +203,7 @@ private:
|
||||
auto tree = build_node_tree();
|
||||
|
||||
// Set up byte and bit count (note, we're compressing the data backwards)
|
||||
auto [bytes, bits] = compressed_size_info();
|
||||
auto [bytes, bits] = compressed_size_info;
|
||||
if (bits > 0)
|
||||
bits = 8 - bits;
|
||||
else
|
||||
@ -201,9 +212,8 @@ private:
|
||||
// Compress data backwards, because we obtain the Huffman codes backwards
|
||||
// as we traverse towards the parent node.
|
||||
for (auto i = raw_data.size(); i > 0; i--) {
|
||||
auto c = static_cast<int>(raw_data[i - 1]);
|
||||
auto leaf = std::find_if(tree.begin(), tree.end(),
|
||||
[c](auto& n) { return n.value == c; });
|
||||
[c = raw_data[i - 1]](auto& n) { return n.value == c; });
|
||||
|
||||
while (leaf->parent != -1) {
|
||||
auto parent = tree.begin() + leaf->parent;
|
||||
@ -225,25 +235,25 @@ private:
|
||||
*/
|
||||
consteval void build_decode_tree() noexcept {
|
||||
auto tree = build_node_tree();
|
||||
auto decode_tree = compressed_data + compressed_size_info().first;
|
||||
auto decode_tree = compressed_data + compressed_size_info.first;
|
||||
|
||||
for (usize_t i = 0; i < tree_count(); i++) {
|
||||
for (usize_t i = 0; i < tree_count; i++) {
|
||||
// Only store node value if it represents a data value
|
||||
decode_tree[i * 3] = tree[i].value <= 0xFF ? tree[i].value : 0;
|
||||
|
||||
usize_t j;
|
||||
// Find the left child of this node
|
||||
for (j = i + 1; j < tree_count(); j++) {
|
||||
for (j = i + 1; j < tree_count; j++) {
|
||||
if (tree[i].left == tree[j].value)
|
||||
break;
|
||||
}
|
||||
decode_tree[i * 3 + 1] = j < tree_count() ? j - i : 0;
|
||||
decode_tree[i * 3 + 1] = j < tree_count ? j - i : 0;
|
||||
// Find the right child of this node
|
||||
for (j = i + 1; j < tree_count(); j++) {
|
||||
for (j = i + 1; j < tree_count; j++) {
|
||||
if (tree[i].right == tree[j].value)
|
||||
break;
|
||||
}
|
||||
decode_tree[i * 3 + 2] = j < tree_count() ? j - i : 0;
|
||||
decode_tree[i * 3 + 2] = j < tree_count ? j - i : 0;
|
||||
}
|
||||
|
||||
delete[] tree.data();
|
||||
@ -251,7 +261,7 @@ private:
|
||||
|
||||
public:
|
||||
consteval static auto compressed_size() noexcept {
|
||||
return compressed_size_info().first + 3 * tree_count();
|
||||
return compressed_size_info.first + 3 * tree_count;
|
||||
}
|
||||
consteval static auto uncompressed_size() noexcept {
|
||||
return raw_data.size();
|
||||
@ -269,14 +279,14 @@ public:
|
||||
|
||||
decoder(const unsigned char *comp_data) noexcept
|
||||
: m_data(comp_data),
|
||||
m_table(comp_data + compressed_size_info().first) { get_next(); }
|
||||
m_table(comp_data + compressed_size_info.first) { get_next(); }
|
||||
decoder() = default;
|
||||
|
||||
constexpr static decoder end(const unsigned char *comp_data) noexcept {
|
||||
decoder ender;
|
||||
ender.m_data = comp_data;
|
||||
if constexpr (bytes_saved() > 0) {
|
||||
const auto [size_bytes, last_bits] = compressed_size_info();
|
||||
const auto [size_bytes, last_bits] = compressed_size_info;
|
||||
ender.m_data += size_bytes - 1;
|
||||
ender.m_bit = 1 << (7 - last_bits);
|
||||
} else {
|
||||
@ -287,9 +297,7 @@ public:
|
||||
}
|
||||
|
||||
bool operator==(const decoder& other) const noexcept {
|
||||
return m_data == other.m_data &&
|
||||
m_bit == other.m_bit &&
|
||||
m_current == other.m_current;
|
||||
return m_data == other.m_data && m_bit == other.m_bit;
|
||||
}
|
||||
auto operator*() const noexcept {
|
||||
return m_current;
|
||||
@ -306,12 +314,8 @@ public:
|
||||
|
||||
private:
|
||||
void get_next() noexcept {
|
||||
if (auto e = end(m_table - compressed_size_info().first);
|
||||
m_data == e.m_data && m_bit == e.m_bit)
|
||||
{
|
||||
m_current = -1;
|
||||
if (*this == end(m_data))
|
||||
return;
|
||||
}
|
||||
if constexpr (bytes_saved() > 0) {
|
||||
auto *node = m_table;
|
||||
int data = *m_data;
|
||||
@ -377,7 +381,7 @@ public:
|
||||
private:
|
||||
// Contains the compressed data, followed by the decoding tree.
|
||||
unsigned char compressed_data[
|
||||
bytes_saved() > 0 ? compressed_size_info().first + 3 * tree_count()
|
||||
bytes_saved() > 0 ? compressed_size_info.first + 3 * tree_count
|
||||
: raw_data.size()] = {0};
|
||||
};
|
||||
|
||||
@ -387,20 +391,5 @@ constexpr auto operator ""_huffman()
|
||||
return huffman_compressor<hsc>();
|
||||
}
|
||||
|
||||
template <detail::huffman_string_container hsc>
|
||||
constexpr auto huffman_compress = huffman_compressor<hsc>();
|
||||
|
||||
namespace detail
|
||||
{
|
||||
template <typename T, T... list>
|
||||
class huffman_compress_array_container {
|
||||
private:
|
||||
constexpr static T uncompressed[] = {list...};
|
||||
public:
|
||||
constexpr static auto data = huffman_compress<uncompressed>;
|
||||
};
|
||||
}
|
||||
template <typename T, T... list>
|
||||
constexpr auto huffman_compress_array = detail::huffman_compress_array_container<T, list...>::data;
|
||||
|
||||
#endif // TCSULLIVAN_CONSTEVAL_HUFFMAN_HPP_
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user