Skip to content

Basic Usage Examples

This page demonstrates fundamental PFC usage patterns for common tasks.

Simple Compression

Compress and decompress a vector of integers:

#include <pfc/pfc.hpp>
#include <vector>
#include <iostream>

using namespace pfc;

int main() {
    // Original data
    std::vector<uint32_t> data = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89};

    // Compress using default codec (EliasGamma)
    auto compressed = compress(data);

    // Decompress
    auto decompressed = decompress<uint32_t>(compressed);

    // Verify
    assert(data == decompressed);

    // Show compression ratio
    std::cout << "Original: " << data.size() * sizeof(uint32_t) << " bytes\n";
    std::cout << "Compressed: " << compressed.size() << " bytes\n";
    std::cout << "Ratio: " << (data.size() * sizeof(uint32_t)) / (double)compressed.size() << "x\n";
}

Output:

Original: 40 bytes
Compressed: 12 bytes
Ratio: 3.33x

Choosing Different Codecs

Different codecs work better for different data distributions:

#include <pfc/pfc.hpp>
#include <random>

using namespace pfc;

int main() {
    std::mt19937 gen(42);

    // Small numbers - use Elias Gamma
    std::geometric_distribution<> small_dist(0.3);
    std::vector<uint32_t> small_nums;
    for (int i = 0; i < 1000; ++i) {
        small_nums.push_back(small_dist(gen));
    }
    auto compressed_gamma = compress<EliasGamma>(small_nums);

    // Medium numbers - use Elias Delta
    std::uniform_int_distribution<uint32_t> med_dist(0, 100000);
    std::vector<uint32_t> med_nums;
    for (int i = 0; i < 1000; ++i) {
        med_nums.push_back(med_dist(gen));
    }
    auto compressed_delta = compress<EliasDelta>(med_nums);

    // Compare
    std::cout << "Small nums with Gamma: " << compressed_gamma.size() << " bytes\n";
    std::cout << "Medium nums with Delta: " << compressed_delta.size() << " bytes\n";
}

Working with Packed Values

Packed values maintain compressed representation:

#include <pfc/pfc.hpp>

using namespace pfc;

int main() {
    // Define a packed integer type
    using PackedInt = Packed<uint32_t, EliasGamma>;

    // Create packed values
    PackedInt a(42);
    PackedInt b(100);

    // Values are stored in compressed form
    std::cout << "Packed size of a: " << a.byte_size() << " bytes\n";
    std::cout << "Packed size of b: " << b.byte_size() << " bytes\n";

    // Decode on access
    uint32_t decoded_a = a.get();
    uint32_t decoded_b = b.get();

    // Comparison works directly
    assert(a != b);

    // Can be copied efficiently
    PackedInt c = a;
    assert(c == a);
}

Packed Containers

Use PackedVector for dynamic collections:

#include <pfc/pfc.hpp>
#include <algorithm>
#include <numeric>

using namespace pfc;

int main() {
    // Create a packed vector
    PackedVector<Packed<uint32_t, EliasGamma>> vec;

    // Add elements
    for (uint32_t i = 1; i <= 10; ++i) {
        vec.push_back(i * i);  // 1, 4, 9, 16, 25, ...
    }

    // Access elements (decoded on demand)
    for (const auto& value : vec) {
        std::cout << value << " ";
    }
    std::cout << "\n";

    // Works with STL algorithms
    auto sum = std::accumulate(vec.begin(), vec.end(), 0u);
    std::cout << "Sum: " << sum << "\n";

    // Sorting
    std::sort(vec.begin(), vec.end(), std::greater<>());

    // Finding
    auto it = std::find(vec.begin(), vec.end(), 25);
    if (it != vec.end()) {
        std::cout << "Found 25 at index " << std::distance(vec.begin(), it) << "\n";
    }
}

Strings

PackedString provides compressed string storage:

#include <pfc/pfc.hpp>

using namespace pfc;

int main() {
    // Create strings
    PackedString str1("Hello, PFC!");
    PackedString str2("Compressed strings");

    // Conversion
    std::string std_str = str1.to_string();

    // Comparison
    assert(str1 != str2);

    // Concatenation (via conversion)
    PackedString str3(str1.to_string() + " " + str2.to_string());

    // Access characters
    for (size_t i = 0; i < str1.size(); ++i) {
        std::cout << str1[i];
    }
    std::cout << "\n";
}

Signed Integers

Use Signed codec wrapper for negative values:

#include <pfc/pfc.hpp>

using namespace pfc;

int main() {
    // Define signed packed type
    using SignedInt = Packed<int, Signed<EliasGamma>>;

    // Create values
    SignedInt positive(42);
    SignedInt negative(-42);
    SignedInt zero(0);

    // All efficiently encoded using zigzag
    std::cout << "Positive size: " << positive.byte_size() << " bytes\n";
    std::cout << "Negative size: " << negative.byte_size() << " bytes\n";
    std::cout << "Zero size: " << zero.byte_size() << " bytes\n";

    // Vector of signed integers
    PackedVector<SignedInt> vec = {-10, -5, 0, 5, 10};

    // Works naturally with signed arithmetic
    for (auto val : vec) {
        std::cout << val << " ";
    }
    std::cout << "\n";
}

File I/O

Save and load packed data:

#include <pfc/pfc.hpp>
#include <fstream>

using namespace pfc;

void save_data(const std::string& filename,
               const PackedVector<Packed<uint32_t, EliasGamma>>& vec) {
    std::ofstream file(filename, std::ios::binary);
    if (!file) {
        throw std::runtime_error("Cannot open file");
    }

    // Write size
    uint32_t size = vec.size();
    file.write(reinterpret_cast<const char*>(&size), sizeof(size));

    // Write packed data directly
    file.write(reinterpret_cast<const char*>(vec.data()), vec.byte_size());
}

PackedVector<Packed<uint32_t, EliasGamma>> load_data(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file) {
        throw std::runtime_error("Cannot open file");
    }

    // Read size
    uint32_t size;
    file.read(reinterpret_cast<char*>(&size), sizeof(size));

    // Read packed data
    std::vector<uint8_t> buffer(size * 10);  // Estimate
    file.read(reinterpret_cast<char*>(buffer.data()), buffer.size());

    // Construct from buffer
    return PackedVector<Packed<uint32_t, EliasGamma>>(buffer.data(), buffer.size());
}

int main() {
    // Create and save
    PackedVector<Packed<uint32_t, EliasGamma>> vec = {1, 2, 3, 5, 8, 13};
    save_data("data.bin", vec);

    // Load
    auto loaded = load_data("data.bin");

    // Verify
    assert(vec == loaded);
}

Network Transmission

Send packed data over network:

#include <pfc/pfc.hpp>
#include <sys/socket.h>

using namespace pfc;

void send_packed_data(int socket, const PackedVector<Packed<uint32_t, EliasGamma>>& vec) {
    // Send size header
    uint32_t size = vec.byte_size();
    send(socket, &size, sizeof(size), 0);

    // Send packed data directly (zero-copy)
    send(socket, vec.data(), size, 0);
}

PackedVector<Packed<uint32_t, EliasGamma>> receive_packed_data(int socket) {
    // Receive size
    uint32_t size;
    recv(socket, &size, sizeof(size), MSG_WAITALL);

    // Receive data
    std::vector<uint8_t> buffer(size);
    recv(socket, buffer.data(), size, MSG_WAITALL);

    // Zero-copy construction
    return PackedVector<Packed<uint32_t, EliasGamma>>(buffer.data(), buffer.size());
}

Configuration Storage

Store configuration with mixed types:

#include <pfc/pfc.hpp>

using namespace pfc;

struct Config {
    PackedVector<Packed<uint32_t, EliasGamma>> allowed_ports;
    PackedString server_name;
    Packed<uint32_t, FixedWidth<32>> flags;
    Packed<float, Float32> timeout;
};

int main() {
    // Create configuration
    Config config;
    config.allowed_ports = {80, 443, 8080};
    config.server_name = PackedString("MyServer");
    config.flags = Packed<uint32_t, FixedWidth<32>>(0xFF00);
    config.timeout = Packed<float, Float32>(30.0f);

    // Each field maintains compressed representation
    std::cout << "Ports size: " << config.allowed_ports.byte_size() << " bytes\n";
    std::cout << "Name size: " << config.server_name.byte_size() << " bytes\n";

    // Access values
    for (auto port : config.allowed_ports) {
        std::cout << "Port: " << port << "\n";
    }
    std::cout << "Server: " << config.server_name.to_string() << "\n";
    std::cout << "Timeout: " << config.timeout.get() << "s\n";
}

Bit-Level Operations

Low-level bit manipulation:

#include <pfc/pfc.hpp>

using namespace pfc;

int main() {
    uint8_t buffer[64] = {};

    // Writing
    {
        BitWriter writer(buffer, sizeof(buffer));

        // Write individual bits
        writer.write_bit(1);
        writer.write_bit(0);
        writer.write_bit(1);

        // Write multiple bits
        writer.write_bits(0b1010, 4);

        // Write with different codecs
        EliasGamma::encode(42, writer);
        EliasDelta::encode(1000, writer);

        // Align to byte boundary
        writer.align();

        std::cout << "Wrote " << writer.bits_written() << " bits\n";
    }

    // Reading
    {
        BitReader reader(buffer, sizeof(buffer));

        // Read individual bits
        bool b1 = reader.read_bit();
        bool b2 = reader.read_bit();
        bool b3 = reader.read_bit();

        // Read multiple bits
        uint32_t nibble = reader.read_bits(4);

        // Read with codecs
        auto v1 = EliasGamma::decode(reader);
        auto v2 = EliasDelta::decode(reader);

        std::cout << "Read values: " << v1 << ", " << v2 << "\n";
    }
}

Next Steps