FM Index
FM-Index full-text index implementation using RRR Wavelet trees (libcds) and fast suffix sorting (libdivsufsort) including experimental results.
Install / Use
npx skills add mpetri/FM-IndexInstalls into whichever agent you are using.
README
FM-Index - Compressed full-text Index
A simple C++ based FM-Index [1] implementation using RRR [4] wavelet trees [5]
which allows to build a full-text index over a given text T of size n
supporting the following operations:
count(P,m): count the number of occurences of patternPof sizeminT.locate(P,m): locate the text positions of all occurences ofPof sizeminT.extract(A,B): extractT[A,B]from the index.recover(): recoverTfrom the index.
The constructed index uses nH_k + o(n log sigma) bits of space [3] which is roughly the size of
the compressed representation of T and can perform the above operations without the need
to store T. An empirical evaluation of the index is sown in the Benchmark section below.
Drawbacks of the FM-Index is long construction time and high memory requirements during construction.
Usage
Compiling the Index
make
Building an Index
./fmbuild alice29.txt alice29.txt.fm
Builds and writes the FM-Index alice29.txt.fm.
Running count() queries
./fmcount -i alice29.txt.fm alice.qry
The queries are stored in a new line seperated file:
the
house
keep
Alice
and
The index returns the number of occurrences for each query:
./fmcount -i alice29.txt.fm alice.qry
the : 2101
house : 20
keep : 11
Alice : 395
and : 880
Running locate() queries
./fmlocate -i alice29.txt.fm alice.qry
The index returns a sorted list of the locations of all occurences for each query:
./fmlocate -i alice29.txt.fm alice.qry
Read 3 queries
keep (11) : 46385 51125 69491 74680 81562 83046 104830 105180 133621 149966 151623
poison (3) : 8151 8619 8731
tomorrow (1) : 63637
Running extract() queries
./fmextract -i alice29.txt.fm alice.extract
The queries are stored in a new line seperated file:
118 147
1213 1245
24 55
The index returns the extracted text snippet for each query:
./fmextract -i alice29.txt.fm alice.extract
118 - 147 : 'THE MILLENNIUM FULCRUM EDITION'
1213 - 1245 : 'TOOK A WATCH OUT OF ITS WAISTCOAT'
24 - 55 : 'ALICE'S ADVENTURES IN WONDERLAND'
Recover the original text from the index
./fmrecover -i alice29.txt.fm
The index outputs the original text to stdout.
Verbose output
The -v command line parameter enables verbose messages:
./fmbuild -v alice29.txt
building index.
- remapping alphabet.
- creating cumulative counts C[].
- performing bwt.
- sample SA locations.
- creating bwt output.
- create RRR wavelet tree over bwt.
build FM-Index done. (0.101 sec)
space usage:
- remap_reverse: 75 bytes (0.07%)
- C: 1028 bytes (0.90%)
- Suffixes: 9508 bytes (8.31%)
- Positions: 9512 bytes (8.31%)
- Sampled: 7948 bytes (6.95%)
- T_bwt: 86088 bytes (75.23%)
input Size n = 152090 bytes
index Size = 114431 bytes (0.75 n)
writing FM Index to file 'alice29.txt.fm'
Using the FM-Index to provide full-text search on a given text T
A small code example illustrating the use of the FM class:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "FM.h"
int
main(int argc,char** argv) {
uint8_t* T = /* read text */
uint32_t n = /* sizeof T */
FM* = new FM(T,n);
if(FM) {
/* count the occurences of 'house' in T */
uint32_t cnt = FM->count("house",strlen("house"));
/* get all locations offsets of 'house' in T */
uint32_t matches; /* number of matches */
uint32_t* locs; /* list of location offsets */
locs = FM->locate("house",strlen("house"),&matches);
/* extract text snippet from T */
uint8_t* snippet = FM->extract(5,251);
/* recover T from index */
uint32_t nnew; /* size of Tnew */
uint8_t* Tnew = FM->reconstructText(&nnew);
delete FM;
}
}
Compile with g++ -o test main.cpp FM.cpp util.c libcds.a libdivsufsort.a
Benchmarks
Experiments are similar to the ones described in [1]. All experiments were run on a Intel(R) Core(TM)2 Duo CPU E8500 @ 3.16GHz using 4GB of RAM.
The default samplerate s=64 was used in all experiments.
Test data
Test data was taken from the The Pizza&Chili Site and TREC.
<table> <tr> <th>File</th><th align=left>Description</th><th>Alphabet size</th><th>Entropy (bps)</th> </tr> <tr> <td>wsj</td><td align=left >English Text taken from the TREC wsj collection</td><td>90</td><td>4.60</td> </tr> <tr> <td>src</td><td align=left>Concatenated source code (.c,.h,.C,.java) of linux-2.6.11.6 and gcc-4.0.0</td><td>230</td><td>5.47</td> </tr> <tr> <td>proteins</td><td align=left>Sequence of newline-separated protein sequences</td><td>25</td><td>4.20</td> </tr> <tr> <td>dna</td><td align=left>Gene DNA sequences</td><td>4</td><td>1.97</td> </tr> <tr> <td>xml</td><td align=left>XML that provides bibliographic info on compsci pubs(dblp)</td><td>96</td><td>5.26</td> </tr> </table>Construction
Peak memory usage was measured using the valgrind --tool=massif tool. Running time was measured using the gettimeofday() system call.
Construction time increases linearly O(n) with size n of T. Memory requirement is roughly 6n independent of the file type.
Index size depends on the compressability of T. For each specific file type, as n increases,
the size of the auxillary data becomes less significant which leads to better overall compression ratio.
Count
50000 random patterns for each length m=5 to 50 were extracted from each 200MB file.

The graph shows the time in seconds it took the FM-Index to answer all 50000 queries on different file types. Note that the query
time grows linearly O(m) with the pattern length. Files with smaller alphabet show faster query times overall.
Also note that the query time (m=20) does not depend on the size n of the text T:
Overall, searching for 50000 patterns in T using a FM-Index is fast but requieres considerable amount of upfront work (time+space).
Locate
k queries pf length 5 are randomly selected from T so they roughly amount to a certain number of total occurences over all queries.

Note the running time increases linearly with the number of occurrences. Similar to count(), files with small alphabet size perform
better most likely due to decreased height of the wavelet tree. Comparing the running times of both count() and locate() we observe
that locate() is significantly slower than count() as patterns (esp
Related Skills
node-connect
385.6kDiagnose OpenClaw Android, iOS, or macOS node pairing, QR/setup code, route, auth, and connection failures.
blender-python-addon
40.5kBlender Python add-on rules for operators, panels, properties, registration, testing, and API-safe scripting
flutter-development-guidelines-cursorrules-prompt-file
40.5kCursor rules for Flutter development with MVVM architecture, Riverpod state management, Material widgets, and Dart style guidelines.
commit-push-pr
140.7kCommit, push, and open a PR
