Learn · The Art of Not Looking
budding
Rank, Select, and Almost No Bits
A compressed set can answer navigation questions without first becoming an array again.
“Our new index is theoretically attractive: it guarantees to code the information in the index close to its information-theoretical lower bound.”
— Sebastiano Vigna, “Quasi-Succinct Indices”
This is the thirteenth chapter in a book about search from first principles. The previous chapter compressed posting gaps and paid to decode them. Here you will distinguish compressed from succinct representations, define rank and select, build a two-level constant-time rank directory, split a monotone sequence into Elias–Fano high and low parts, recover any value with one select, derive the ceiling-aware space bound, and explain when partitioning helps. You will also identify the static-update and small-list crossovers. The next chapter follows the index across the network and separates transfer, validation, construction, retention, and query costs.
An operation can survive compression
Let a bitvector mark occupied hotel rooms:
room: 0 1 2 3 4 5 6 7 8 9
guest: 0 1 0 1 1 0 0 1 0 0
Two questions recur:
With zero-based occurrence indices, the example gives and .
Rank maps a position to an ordinal. Select maps an ordinal back to a position. Membership follows from two ranks:
Predecessor follows from rank then select. These are not ornamental methods; they are a compact instruction set for navigating sets, trees, wavelet structures, and compressed text indexes.
Succinct data structure — a representation near the information-theoretic minimum that still supports its declared operations efficiently. The word is a formal space claim, not praise for tidy code. Learn more.
Constant-time rank by remembering two scales
A full prefix-count array answers rank in constant time but stores about bits for a bitvector of length —larger than the data. Remember counts sparsely instead.
Choose superblocks of roughly bits and blocks of roughly bits.
- Each superblock stores the absolute number of ones before it.
- Each block stores the number of ones since its superblock began.
- The final short block is counted by a word popcount or a shared lookup table.
Then
There are superblocks, each using bits, for bits. There are blocks, each using bits, for bits. Both are .
The data and directory therefore occupy
bits while rank uses a constant number of word operations in the word-RAM model. Constant-time select uses an analogous multiscale directory with special handling for dense and sparse regions; its proof is more involved, but its extra space is also lower order.
Store answers at a coarse scale, offsets at a finer scale, and solve only the small residue directly.
Split a monotone number into high and low evidence
Let
Choose a low-bit width
when , and otherwise. Split each value:
Pack all lows in bits. The highs are nondecreasing. Encode them in a unary bitvector by placing the $i$th one at
Zeros represent increases of the high quotient; ones represent sequence elements. This is the Elias–Fano split.
For in universe , and :
| high | low | one position | ||
|---|---|---|---|---|
| 0 | 3 | 0 | 3 | 0 |
| 1 | 5 | 1 | 1 | 2 |
| 2 | 8 | 2 | 0 | 4 |
| 3 | 14 | 3 | 2 | 6 |
Thus through its final one, and the packed lows are .
Select reconstructs any value
The $i$th one position satisfies
Therefore
and
One select, one packed-field read, one subtraction, and one composition recover the $i$th value without decoding its predecessors.
High quotients also delimit buckets. To seek a target, locate its high bucket in , then search only the corresponding contiguous lows. Practical quasi-succinct indexes add forward pointers or sampling so predecessor and nextGEQ do not scan long unary regions.
The exact space bound before the slogan
With , the high quotient universe has at most
values. The unary high bitvector therefore needs at most bits: at most zero positions and exactly one positions. The lows need bits. The exact ceiling-aware construction bound is
This is commonly summarized as about
bits, or bits per element, ignoring the at-most-one-bit rounding increase in each low field. The summary is useful; it is not an exact byte count for arbitrary .
Compare it with Chapter 12's sparse-set floor:
At power-of-two ratios, the idealized Elias–Fano excess is approximately
bits, before rank/select directories and alignment. With ceiling and finite effects, the excess is larger but still bounded by a small constant per value. That is the real claim: near the counting floor while retaining direct access.
Some presentations choose . That saves low bits but permits a longer high bitvector. Both choices are valid; do not combine one choice's low cost with the other's high bound.
Partition when the local universe is different
A posting list can be globally sparse and locally clustered. One global ratio gives every element the same low width even when one region is dense and another contains a long jump.
Partitioned Elias–Fano divides the monotone sequence into chunks. For each chunk, subtract its base and encode its local range and count. Dynamic programming or a practical approximation chooses boundaries that trade:
- local Elias–Fano payload;
- partition headers and bases;
- boundary lookup cost;
- sequential and random query behavior.
A partition is worthwhile when reduced local universes repay their headers. Uniform data may gain nothing. Tiny partitions degenerate into metadata.
Succinct does not mean universally fast
Compare three representations:
| Representation | Strength | Cost |
|---|---|---|
| flat integers | direct, simple, updateable copy | fixed machine width |
| gap-coded blocks | strong compression, fast sequential decode | predecessors or restarts needed |
| Elias–Fano | near-floor space, select/direct access, monotone seek | rank/select directories and bit arithmetic |
Elias–Fano is static in this chapter. A single insertion can move packed lows, high bits, and directories, so a build-emitted index normally rebuilds or replaces a segment. Dynamic succinct structures exist, but their additional indirection and constants are a separate design, not a free mutation method.
For a tiny list, a flat array can win on bytes once headers and directories are counted, and on time because one cache line and a few comparisons beat a succinct traversal. For dense universes, an ordinary bitvector with rank/select may be simpler. For sequential scoring, a block codec may decode faster than repeated select. Measure the actual operation mix.
Elias–Fano access
ELIAS-FANO-SELECT(HIGH-BITS, LOWS, WIDTH, INDEX)
Input: unary HIGH-BITS with select support, packed LOWS, WIDTH, valid INDEX
Output: monotone value at INDEX
position ← SELECT-ONE(HIGH-BITS, INDEX)
high ← position - INDEX
low ← READ-PACKED(LOWS, INDEX, WIDTH)
return (high << WIDTH) OR lowThe decoder validates , , width, high-bit length, low payload extent, one count, and monotonic reconstruction before publication. A rank/select directory is derived from the same immutable bitvector generation; a directory paired with different bits is not merely stale—it answers a different set.
What to test and measure
Prove the representation against a flat sorted oracle:
- select reconstructs every generated value;
- reconstructed values are strictly increasing and below ;
- rank/select membership agrees with the flat set;
nextGEQagrees on present, absent, boundary, and exhausted targets;- serialized high bits and lows match pinned small examples;
- partitioned and unpartitioned forms answer identically;
- corrupted extents, counts, widths, and one totals are rejected.
Measure exact payload, directory, alignment, and header bytes separately. Benchmark sequential iteration, random select, predecessor, and intersection at tiny, cache-resident, and memory-resident sizes. The static-search layout lesson applies: batched throughput can hide memory latency that one interactive lookup must pay.
Lessons
- Rank counts marked positions before an index; select locates a marked ordinal.
- Multiscale directories make rank constant-time with lower-order extra bits.
- Elias–Fano stores low remainders directly and high quotients as unary gaps.
- The $i$th value is recovered from and its packed low field.
- With a ceiling low width, the exact construction uses at most bits before directories.
- The familiar statement is an idealized summary, not an exact arbitrary-ratio byte count.
- Partitioning pays only when local clustering recovers more bits than its boundaries cost.
- Succinct describes space plus operations; it does not promise the fastest implementation for every workload.
- Static immutability is appropriate for shipped index generations and a poor fit for frequent point updates.
Practice
- Build the superblock, block, and tail components for one rank query over a 32-bit example.
- Elias–Fano encode with . Write , highs, lows, , and reconstruct every value.
- Prove the high bitvector has at most bits under the ceiling-width choice.
- Find a non-power-of-two ratio where replacing the ceiling by the real logarithm understates actual low storage.
- Construct a clustered list for which two partitions save payload, then add a header cost large enough to erase the win.
- Design a crossover benchmark among flat integers, a gap codec, a bitvector, and Elias–Fano for sequential and random operations.
References
- Guy Jacobson. Succinct Static Data Structures. PhD thesis, Carnegie Mellon University, 1988.
- Sebastiano Vigna. “Quasi-Succinct Indices.” Proceedings of WSDM 2013, pp. 83–92.
- Giuseppe Ottaviano and Rossano Venturini. “Partitioned Elias–Fano Indexes.” Proceedings of SIGIR 2014, pp. 273–282.
- Gonzalo Navarro. Compact Data Structures: A Practical Approach. Cambridge University Press, 2016.