compactly 0.1.8

Compactly encode data types using adaptive arithmetic coding
Documentation
//! Focused benchmark for the per-character string-decode hot path (the
//! `char`/`u8` tree walk): decode a `BTreeSet<String>` of meteorite
//! names repeatedly.
//!
//! Usage: `just-decompress-strings [ans|range] [iterations]` (defaults:
//! `ans`, 2000). Reads the names from `comparison/src/meteorites.csv`
//! (falling back to `../comparison/src/meteorites.csv`), so run it from the
//! workspace root, like `just-decompress-net` and its `ipv6.txt`.

use std::collections::BTreeSet;

/// Extract the first (quote-aware) CSV field of each record, skipping the
/// header row. Good enough for the meteorite names; avoids a csv dependency.
fn first_fields(csv: &str) -> BTreeSet<String> {
    let mut out = BTreeSet::new();
    for line in csv.lines().skip(1) {
        let name = if let Some(quoted) = line.strip_prefix('"') {
            let Some(end) = quoted.find('"') else {
                continue;
            };
            quoted[..end].to_string()
        } else {
            match line.split_once(',') {
                Some((first, _)) => first.to_string(),
                None => line.to_string(),
            }
        };
        if !name.is_empty() {
            out.insert(name);
        }
    }
    out
}

fn main() {
    let coder = std::env::args()
        .find(|a| a == "ans" || a == "range")
        .unwrap_or("ans".to_string());
    let iterations: usize = std::env::args()
        .filter_map(|a| a.parse().ok())
        .next()
        .unwrap_or(2000);
    let csv = std::fs::read_to_string("comparison/src/meteorites.csv")
        .or_else(|_| std::fs::read_to_string("../comparison/src/meteorites.csv"))
        .expect("run from the workspace root so comparison/src/meteorites.csv is found");
    let names = first_fields(&csv);
    println!("decoding {} meteorite names with {coder}", names.len());

    let mut total = 0usize;
    match coder.as_str() {
        "ans" => {
            let encoded = compactly::v2::Ans::encode(&names);
            println!("encoded size {}", encoded.len());
            for _ in 0..iterations {
                total += std::hint::black_box(
                    compactly::v2::Ans::decode::<BTreeSet<String>>(&encoded).unwrap(),
                )
                .len();
            }
        }
        "range" => {
            let encoded = compactly::v2::encode(&names);
            println!("encoded size {}", encoded.len());
            for _ in 0..iterations {
                total += std::hint::black_box(
                    compactly::v2::decode::<BTreeSet<String>>(&encoded).unwrap(),
                )
                .len();
            }
        }
        _ => unreachable!(),
    }
    println!("total decoded {total}");
}