TCF (Tabular Compact Format) — string compression via OBAT (Online Bidirectional Affix Tokenizer) + HCC (Hierarchical Compositional Coding)
Project description
TCF · Tabular Compact Format
E se desse pra transmitir a mesma tabela com bem menos bytes, sem virar um arquivo binário que ninguém mais consegue abrir e ler?
Um cadastro pequeno, nos três formatos (bytes reais, saída de verdade):
JSON (480 B): repete o nome de cada campo em toda linha.
[ { "nome": "Ana Souza", "email": "ana@acme.com.br",
"cidade": "Sao Paulo", "plano": "Premium" },
{ "nome": "Bruno Lima", "email": "bruno@acme.com.br",
"cidade": "Sao Paulo", "plano": "Premium" }, … ]
CSV (213 B): tira os nomes repetidos, uma linha por registro.
nome,email,cidade,plano
Ana Souza,ana@acme.com.br,Sao Paulo,Premium
Bruno Lima,bruno@acme.com.br,Sao Paulo,Premium
Carla Nunes,carla@acme.com.br,Sao Paulo,Basic
Diego Rocha,diego@acme.com.br,Rio de Janeiro,Premium
TCF (177 B, formato 0.7, saída real do encode): o que se repete vira referência.
#TCF.7 M
!44=nome,42=email,28=cidade,plano
Ana Souza
Bruno Lima
Carla Nunes
Diego Rochaan*a*@acme.com.br
brun*o3
carl2,3
dieg5,3
*3|Sao Paulo
Rio de Janeiro
*2|Premium
Basic
^1
Como ler:
- Linha 1, shebang:
#TCF.7 Mé o formato 0.7, multi-coluna. - Linha 2, meta das colunas (
tamanho=nome). O!marca uma coluna guardada crua (quando o raw fica menor que o TCF). A última (plano) não leva tamanho: vai até o fim. - Os corpos vêm concatenados, delimitados por tamanho, não por quebra de linha.
Por isso a coluna crua
nome(…Diego Rocha) emenda direto no e-mail (an*a*…). - No corpo:
*3|Sao Pauloé "Sao Paulo, 3×" (repetição).^1é "igual à linha 1" (substituição). - Na coluna de e-mail o TCF vai mais fundo (prefixo único + domínio comum referenciado). É onde mais economiza, e onde o texto fica mais denso.
JSON repete a estrutura inteira. CSV repete os valores. O TCF fatora o que se repete e referencia o resto, continuando texto ASCII que você abre e lê.
Mas quanto mais fundo ele fatora (veja o e-mail), mais denso o texto fica. Legível não quer dizer óbvio à primeira vista.
Em tabelas grandes a diferença cresce: ver Resultados.
O que é o TCF
Um formato textual e sem perdas (decode(encode(x)) == x) para tabelas de strings.
Comprime parecido com um zip/gzip, mas com uma diferença: o resultado continua texto ASCII que você abre e inspeciona, sem descomprimir. Não fica tão óbvio quanto o original (quanto mais o TCF fatora, mais denso o texto), mas nunca vira um blob opaco. Cada coluna passa por um pipeline próprio.
É essa a faixa que o TCF ocupa: compacto como um compressor, inspecionável como texto. (Precisa de ratio máximo? Dá pra rodar gzip/brotli por cima: eles se compõem.)
Como ele faz isso: OBAT + HCC
Duas camadas, explicadas pelo propósito (specs: docs/algorithms/).
OBAT (Online Bidirectional Affix Tokenizer) acha o que as strings têm em comum. Para cada valor, procura o maior prefixo e sufixo compartilhado com os anteriores (domínios de e-mail, raízes de URL, códigos da mesma família). Escreve o trecho uma vez e referencia o resto.
É um front-coding bidirecional: generaliza o front-coding clássico de dicionários de strings (Witten et al.; HTFC/RPDac, Brisaboa et al.).
O "bidirecional" é o que captura o sufixo comum (@acme.com.br), não só o prefixo.
A busca por afixos é da família das árvores de prefixo/sufixo: tries, Patricia/radix tree (Morrison 1968), suffix trees. Na prática o OBAT acelera essa busca com um índice de trigramas, que derruba o custo de O(N²) ingênuo para ~O(N^1.42) (sub-quadrático, quase-linear). (Trocar o índice por uma Patricia trie é candidato futuro: exploração.)
HCC (Hierarchical Compositional Coding) decide o que vale a pena nomear e agrupa repetições.
Pega os tokens do OBAT, fatora composições recorrentes em referências nomeadas reutilizáveis (operador ~) e colapsa repetidos (RLE, inclusive sequências quase-iguais, tipo IDs que só mudam no fim).
Como referência aponta para referência, o resultado é um grafo acíclico (DAG) de fragmentos: na prática uma gramática / straight-line program do conteúdo.
É o espírito do Re-Pair (Larsson & Moffat 1999) e do Sequitur (Nevill-Manning & Witten 1997), mas operando sobre os tokens do OBAT (não sobre bytes) e com operadores próprios (~ cria nó nomeado, , só concatena).
É o que mantém a saída pequena e inspecionável: os grupos *N|... ficam à vista.
Velocidade. O lado caro é o encode (a busca de afixos do OBAT), trazido a quase-linear pelo índice de trigramas (mais o acelerador Cython opcional). O decode é uma passada linear única: só expande as referências (lookups O(1)) e os grupos RLE, sem nenhuma busca. Rápido e previsível.
Getting started (1 minuto)
from tcf import encode, decode
# Single-column: lista de strings
text = encode(["joao@gmail.com", "maria@gmail.com", "pedro@gmail.com"])
assert decode(text) == ["joao@gmail.com", "maria@gmail.com", "pedro@gmail.com"]
# Multi-column: dict de colunas
table = {
"id": ["1", "2", "3"],
"email": ["joao@gmail.com", "maria@gmail.com", "pedro@gmail.com"],
}
text = encode(table)
assert decode(text) == table # round-trip lossless
# Naturezas (opt-in): CPF/CNPJ/IP comprimidos sem digito verificador/padding
from tcf import SPEC_CPF
text = encode(["111.444.777-35", "529.982.247-25"], nature=SPEC_CPF)
encode dispatcha por tipo (list → single-column, dict → multi-column).
decode roteia pelo shebang.
Tutorial passo-a-passo: docs/tutorials/getting-started.md.
Guias praticos: docs/how-to/.
Formato 0.7 (default): onde os bytes vão
O encode multi-coluna sai em 0.7 / #TCF.7 por default (ADR-0024).
Quatro coisas, todas automáticas (sem flag), cada coluna escolhendo a menor representação:
- Fallback por coluna.
Guarda a coluna em raw quando o raw fica menor que o TCF ("nunca pior que raw").
Marcada com
!no meta (ADR-0022). - Dicionário low-card.
Coluna com poucos valores distintos vira tabela de únicos + índices compactos,
em vez de um ref por linha.
Marcada com
@no meta (ADR-0025). - Split estrutural.
Valor estruturado (decimal, data, datetime, CPF) com template uniforme vira
campos separados (o template guardado uma vez), e cada campo low-card cai no dicionário.
Marcada com
%no meta (ADR-0026). - Header mínimo.
O flag
Mno shebang já declara que vêm colunas, então o meta dispensa o prefixo#. E a última coluna não leva tamanho, vai até o fim (ADR-0023).
text = encode(table) # 0.7 / #TCF.7, é o default, sem flags
# knobs opt-out (default True) — pra modificar o comportamento / inspecionar:
text = encode(table, fallback=False, min_header=False) # força o legado #TCF.6
text = encode(table, min_header=False) # #TCF.7 com header verboso
text = encode(table, min_len=5) # override do min_len do OBAT (default: auto)
text = encode(table, sort_by="cidade") # ordena linhas pela coluna (order-free, +compressão)
sort_byreordena as linhas pela coluna (agrupa similares → menos bytes, 5-15% com chave low-card). É order-free: odecodedevolve a ordem ordenada, não a original. Use só quando a ordem das linhas não importa.
No cadastro de 4 colunas do topo, comparado ao formato legado #TCF.6:
| formato | meta line | bytes |
|---|---|---|
0.7 / #TCF.7 (default) |
!44=nome,42=email,28=cidade,plano |
177 |
#TCF.6 (legado) |
# 45=nome,42=email,28=cidade,20=plano |
182 |
O ganho é proporcionalmente maior em payloads pequenos (o header de tamanho fixo domina).
Pré-1.0, o encoder só escreve o formato mais novo.
O #TCF.6 legado ainda é lido pelo decoder, e git checkout reproduz a era 0.6 (ADR-0024).
O dicionário low-card (V2-B) e o split estrutural já estão no default; a compressão lossy fica no roadmap.
Estado (pré-1.0)
- Pré-1.0 (ADR-0024).
Os minors do formato (
#TCF.4/.5/.6/.7) são iterações de desenvolvimento rumo a um 1.0 sólido, sem compat rígida entre eles (git reproduz versões antigas). v2.0 fica pra depois. - Implementação canônica em
src/tcf/. Round-trip sempre lossless (decode(encode(x)) == x). - Default 0.7 /
#TCF.7: fallback (ADR-0022) + header mínimo (ADR-0023), ver seção acima. O#TCF.6legado é lido pelo decoder. - Suíte: 398 passed, 1 xfailed. Baselines de byte = guardas de regressão, re-pináveis em mudança intencional (ADR-0024).
- Mudanças:
CHANGELOG.md. História M0-M14:experiments/lab/dirty/notas/historia-dirty-lab.md.
O ciclo v0.5 (formato columnar para LLM benchmark) é acessório e vive separado. Ver a seção "Benchmark LLM v0.5" mais abaixo.
Resultados
Sem nenhum compressor, o TCF é o formato de texto mais compacto do conjunto. Nos 15 datasets sintéticos do EXP-008:
| formato (texto puro, sem compressor) | bytes |
|---|---|
| TCF | 3131 |
| CSV | 4872 |
| JSON | 5409 |
| JSONL | 7001 |
~36% menor que CSV e ~42% menor que JSON, continuando legível.
Núcleo pinado em testes: D1-D9 = 1523 B (51.1% do raw, single-col); D17a multi-col = 303 B (0.7 com V2-B; legado #TCF.6 = 322 B).
Real-world multi-coluna (9 tabelas Adult + TPC-H, 136k linhas): −33.02% weighted vs CSV raw.
E contra gzip / brotli / zstd?
Outra categoria: são compressores binários opacos (precisa descomprimir pra ler qualquer coisa).
No ratio puro eles ganham (no EXP-008, csv+brotli = 1742 B contra tcf+brotli = 2141 B).
O TCF troca um pouco de ratio por legibilidade e se compõe com eles (rodar gzip por cima do TCF funciona).
Tabelas completas: reports do EXP-008.
First-time setup (dev)
# Clone + install dev deps
git clone https://github.com/LeoPR/TCF.git && cd TCF
pip install -e ".[dev]"
# (recomendado) instalar pre-commit hooks
pre-commit install
# Rodar hooks em todos arquivos (opcional, baseline)
pre-commit run --all-files
Hooks configurados (ver .pre-commit-config.yaml):
rufflint + formatdetect-secrets(scan)- basicos: trailing-whitespace, end-of-file-fixer, check-merge-conflict, check-added-large-files
- custom: bloqueia cache dirs (
__pycache__/,.pytest_cache/, etc.) acidentalmente staged
How to cite
Ver CITATION.cff. GitHub renderiza badge "Cite this
repository" na pagina do repo automaticamente.
Benchmark LLM v0.5 (acessorio, projeto paralelo)
Esta secao resume o ciclo v0.5 (formato columnar para consumo por LLMs). NAO e' o algoritmo TCF v0.6 acima. Todo o material vive separado.
O ciclo v0.5 mediu compreensao de tabelas por LLMs (CSV/JSON/TOON/TCF, Linha A "LLM le e computa" + Linha B "LLM gera SQL"): 7 modelos comerciais
- 13 locais, 2 datasets, 2256 registros, 38 findings.
Usava o motor de niveis (
EncodeConfig(level=N)) emold/tcf/. Verold/tcf/LEVELS-REVIEW.mdpara a semantica L0–L3.
- Harness (runners, llm_eval, scripts):
llm-benchmark/ - Catalogo de achados F-Q01..Q38:
docs/findings/ - Manual / paper v0.5:
docs/archive/manual_v05/
Candidato a spin-off (tcf-llm-tools) no futuro. Pode re-validar contra v0.6
se Phase 2 for revivida.
Repository layout
TCF/
├── src/tcf/ ← CANONICAL v0.6 API (OBAT+HCC, encode/decode, #TCF.6)
├── old/tcf/ ← motor v0.5 (niveis L0–L3), congelado-historico (ver LEVELS-REVIEW.md)
├── scripts/ ← Shaper (stratified sampling), CSV→SQLite, setup_* datasets
├── experiments/lab/ ← labs v0.6 (dirty + clean): compressao composicional
├── llm-benchmark/ ← benchmark LLM v0.5 (harness: runners + llm_eval), acessorio
├── tests/ ← pytest suite (v0.6)
├── datasets/ ← canonical metadata + samples (dados reais em Z:)
├── tickets/ ← planejamento markdown (YAML frontmatter)
├── docs/
│ ├── algorithms/ ← specs canonicos v0.6 (OBAT, HCC, TCF-format) [reference]
│ ├── adr/ ← decisoes numeradas, imutaveis
│ ├── theory/ ← fundamentos teoricos [explanation]
│ ├── how-to/, tutorials/ ← Diataxis
│ ├── findings/ ← catalogo cientifico v0.5 LLM (F-Q01..Q38) [historico]
│ ├── workbench/ ← dev timeline, research notes (partes em _archive/)
│ └── archive/ ← material v0.5/v0.1 congelado (manual_v05, article_v05, etc.)
├── config/ ← storage.json (aponta Z:), api_keys (gitignored)
├── README.md ← you are here
└── CHANGELOG.md ← release history
Para o mapa detalhado, ver MAP.md. Os diretorios
docs/manual/edocs/article/NAO existem; o material v0.5 correspondente esta emdocs/archive/manual_v05/edocs/archive/article_v05/.
Tools shipped (v0.6)
O encoder e' a ferramenta principal; auxiliares de suporte (NAO TCF-core):
- Shaper (
scripts/shaper/): stratified, FK-preserving sampling framework. Standalone-able as a separate library; see shaper-as-standalone-tool note - DatasetReader (
scripts/dataset_reader.py): uniform interface over SQLite hubs (rows, columns, query, column_stats) - setup_*.py (
scripts/): download/geracao dos datasets canonicos (Adult, TPC-H, IBGE, CNPJ, etc.); ver datasets/README.md
Pré-1.0: library-only (sem CLI; ver
pyproject.toml). O benchmark LLM v0.5 (CommercialClient, M-series runners) vive emllm-benchmark/, com instrucoes de reproducao no README de la'.
Where to go next
- I want to use TCF in my pipeline → API v0.6:
from tcf import encode, decode(src/tcf/); manual v0.6 pendente. v0.5: docs/archive/manual_v05/ - I want to read the findings → docs/findings/ (v0.5 LLM, historico)
- I want to run the LLM benchmark → llm-benchmark/ (acessorio v0.5)
- I want to understand the architecture → docs/theory/
- I want to read the paper → drafts v0.5: docs/archive/article_v05/ (paper v0.6 pendente)
- I want to see how it evolved → CHANGELOG.md + docs/workbench/
License
MIT. See LICENSE.
Acknowledgements
Project conceived as part of an academic dissertation (TCC). Datasets: UCI Adult Census and TPC-H (via DuckDB tpch extension). (Ciclo v0.5) Commercial LLM testing supported by personal credits; total spend $9.46 USD for 1968 records (75% cache savings).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tcf_format-0.7.1.tar.gz.
File metadata
- Download URL: tcf_format-0.7.1.tar.gz
- Upload date:
- Size: 68.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ebe95aa9ee711067542f562a8fb6726939e37071edf14b799cb518786815336d
|
|
| MD5 |
1de24ad8dcb14f22c3881db421e06398
|
|
| BLAKE2b-256 |
a21646b0c5a152d4e4052538283a6d4d562cec5ed43d3340352e2aad17600241
|
File details
Details for the file tcf_format-0.7.1-py3-none-any.whl.
File metadata
- Download URL: tcf_format-0.7.1-py3-none-any.whl
- Upload date:
- Size: 62.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c71471a457a0c5f071171ed53c853bd129e0fc0154579bf8613de322a70516f
|
|
| MD5 |
3b6103d1797f05cd91ff892cef72315c
|
|
| BLAKE2b-256 |
67a89235ab59959752bd57493c8b0f3d1ac2f661312817a6e7a08de765079cb2
|