The data that is not a number

The occurrences that cross a boundary

One pattern, thirty-two copies of a text, thirty-two occurrences. The search finds one of them and produces the other thirty-one by arithmetic, and the count it finds is the same one at two copies, at eight and at thirty-two — the searching does not grow when the answer does.

An occurrence of a pattern lies somewhere in the text. Either it fits inside a single phrase of the parse, or it straddles at least one phrase boundary. There is no third case, and the two cases have completely different costs.

Measured on a collection of thirty-two copies with an eight-character pattern: one occurrence of the first kind and thirty-one of the second. The search looks for the one.

The occurrences a search finds, and the ones it producesOne 8-character pattern in a collection that grows by repeating itself. The occurrences crossing a phrase boundary — the primaries, which are the only ones the search machinery looks for — stay at 1 however many copies there are. Every other occurrence is inside a phrase, so it is a copy of one already found, and it is produced by adding the phrase's displacement rather than searched for. At 32 copies that is 31 of the 32. Both axes are logarithmic.1,00010,000110occurrences12481632characters in the collection · copies aboveall occurrencessecondaryprimarypattern "ss is un" · z = 1561 primary · 31 secondary
Fig. 1 The two kinds across a collection that grows by repeating itself. The lower line is what the search machinery looks for and it does not move; the answer does.

The argument, which is a definition rather than a heuristic

A phrase is the longest piece of what remains that has already occurred earlier in the text. So the characters of a phrase are, by construction, a copy of characters that appeared before it.

An occurrence lying entirely inside one phrase is therefore a copy of a string that occurred earlier — specifically at the position the phrase’s source points to, offset by however far into the phrase the occurrence sits. That earlier string is an occurrence too, of the same pattern, and it is either primary or itself a copy of a still earlier one.

Following that chain down terminates, because each phrase’s source is strictly earlier than the phrase. So every occurrence is reachable from a primary one by following copies forwards, and a search that finds every primary occurrence and then propagates has found everything.

This is the reason the structure can be proportional to zz at all. Searching nn positions would need something proportional to nn; searching zz boundaries needs something proportional to zz, and the argument above is what says that is enough.

Finding the primaries

A primary occurrence crosses a boundary, so it can be located by asking, for each boundary and each way of splitting the pattern, whether the left part ends there and the right part starts there.

There are m1m-1 split points and the two questions are answered by two binary searches — one over the boundaries sorted by the suffix that begins there, one over the boundaries sorted by the reversed text that ends there. The result is two ranges, and a boundary in both is a primary occurrence.

The cost of that is m1m-1 pairs of binary searches, each comparison producing characters from the parse, plus the work of intersecting the two ranges. On the collection above the whole primary stage examines three candidates and performs 272 character comparisons, and it examines the same three candidates at two copies and at thirty-two.

What the two kinds of occurrence cost to produceThe candidates examined during one search, split by which stage examined them. The primary stage's work is flat — the boundaries do not multiply when the collection does — and the propagation's is not, because it walks the phrases for every occurrence it has already found. This is the work the published indexes reduce with a two-dimensional range structure over the z boundaries; it is counted here rather than assumed away, so the plate shows what the structure would be for.1 copy1051 primary, 1 found2 copies2453 primary, 2 found4 copies5193 primary, 4 found8 copies1,0673 primary, 8 found16 copies2,1633 primary, 16 found32 copies4,3553 primary, 32 foundEnglish-like · pattern of 8candidates examinedz = 156
Fig. 2 The candidates each stage examines. The primary stage is flat because it is a function of z and m, and neither of them moves when copies arrive.

Producing the secondaries

Given an occurrence at position pp, every phrase whose source region contains [p,p+m)[p, p+m) produces an occurrence at that phrase’s own start plus the offset. That is not a search: it is an addition, once the phrase is known.

Finding the phrases is a range query over the sources, and the queue is run to fixpoint because a copy can itself be copied. A collection of thirty-two copies produces its occurrences in a chain thirty-one long, each one discovered from the one before it.

The published structures answer that range query with a second two-dimensional grid. This one walks the phrases in source order and counts what it examines, which is 4,352 phrases to produce 31 occurrences at thirty-two copies and 242 to produce one at two copies. The ratio is what a grid is for, and reporting it is how a plate says which structure it drew.

What the two kinds of occurrence cost to produceThe candidates examined during one search, split by which stage examined them. The primary stage's work is flat — the boundaries do not multiply when the collection does — and the propagation's is not, because it walks the phrases for every occurrence it has already found. This is the work the published indexes reduce with a two-dimensional range structure over the z boundaries; it is counted here rather than assumed away, so the plate shows what the structure would be for.1 copy583 primary, 1 found2 copies1163 primary, 2 found4 copies2303 primary, 4 found8 copies4583 primary, 8 found16 copies9143 primary, 16 found32 copies1,8263 primary, 32 founda text that repeats itself · pattern of 8candidates examinedz = 75
Fig. 3 The same two stages on an internally repetitive text. The propagation’s work grows with the answer in both cases, and the searching’s does not grow at all.

What the split says about where a search’s cost goes

The two stages are so different in shape that a single “search cost” for this structure would be misleading in both directions.

The primary stage’s cost depends on zz, on mm and on the copy depths the binary searches happen to land in. It does not depend on how many occurrences there are — a pattern with one occurrence and a pattern with a thousand cost the same to search for.

The secondary stage’s cost is per occurrence produced and depends on nothing else. It is the analogue of the r-index’s φ\varphi step, which is also one operation per answer, and the two structures are closer here than anywhere: both have separated the question “does this occur” from the question “where”, and both charge the second per answer.

The difference is that the r-index gets its first occurrence free, out of the search, as the occurrence carried through the search measures, while the parse-based index has to search for every primary and there may be many.

The occurrences a search finds, and the ones it producesOne 12-character pattern in a collection that grows by repeating itself. The occurrences crossing a phrase boundary — the primaries, which are the only ones the search machinery looks for — stay at 1 however many copies there are. Every other occurrence is inside a phrase, so it is a copy of one already found, and it is produced by adding the phrase's displacement rather than searched for. At 32 copies that is 31 of the 32. Both axes are logarithmic.1,00010,000110occurrences12481632characters in the collection · copies aboveall occurrencessecondaryprimarypattern "ss is until " · z = 1561 primary · 31 secondary
Fig. 4 The same collection with a longer pattern. Longer patterns cross more boundaries, so the primary count rises and the propagation has more starting points.

Two costs that are not comparable, drawn on one plate anyway

The stages’ costs are in different units — character comparisons for the search, phrases examined for the propagation — and this collection’s rule is that such things are not summed. The plate that shows both draws them as one bar split into two parts and names each, which is the compromise, and it is worth saying why the compromise is acceptable here.

Both quantities are candidates: things the machinery looked at and mostly discarded. A candidate boundary in the intersection and a candidate phrase in the propagation are the same kind of act at the same level of the structure, and neither of them is a text read or a bit-vector rank. Summing them gives the number of times the structure examined something, which is a quantity a reader can use.

What is not summed anywhere is those candidates and the chain walks they cost. A comparison during a binary search examines one candidate and follows a chain of whatever depth that position has, and the depths run from two to thirty-eight on this collection. So the plates report candidates and chain steps separately, in the way one run, two counts established for this collection’s first pair of counters.

The chain a search walks, per occurrence returnedEvery character the search compares is produced by following a phrase back to the one it was copied from, so the binary searches over the boundaries are paid for in copies followed. Divided by the occurrences returned, the cost falls as the collection grows — the same search answers a larger question — which is the one direction in which repetition helps this structure rather than costing it.1 copy286.0286 copies, 230 cmp2 copies152.5305 copies, 240 cmp4 copies76.3305 copies, 240 cmp8 copies38.1305 copies, 240 cmp16 copies19.1305 copies, 240 cmp32 copies9.5305 copies, 240 cmpa text that repeats itself · pattern of 8copies followed per occurrence32 occurrences
Fig. 5 The chain steps, which are the other unit. They are what a candidate costs and they are not in the candidate counts on the plate above.

The failure this invites

There is a specific way to get this wrong that no ordinary check catches, and it is the reason the gate for this structure compares against exhaustion.

Report only the primaries. They are all genuine occurrences: every one of them was found by matching the pattern’s two halves against real text. Nothing about them is wrong except that there are far too few — one against eight on a collection of eight copies, measured, and one against thirty-two on a collection of thirty-two.

A check asking “is every reported position an occurrence” passes. A check asking “does the count match the interval width” has nothing to compare against, because there is no interval. A check comparing against another index catches it, and a check comparing against a scan of the text catches it, and nothing else does.

This collection has met that shape before in a filter rather than an index: the filter that proposes everything is about a stage whose output is correct and useless, and the rejection test there is the same one — feed the machinery a version that skips a stage and require the gate to notice.

The chain a search walks, per occurrence returnedEvery character the search compares is produced by following a phrase back to the one it was copied from, so the binary searches over the boundaries are paid for in copies followed. Divided by the occurrences returned, the cost falls as the collection grows — the same search answers a larger question — which is the one direction in which repetition helps this structure rather than costing it.1 copy410.0410 copies, 254 cmp2 copies216.5433 copies, 272 cmp4 copies108.3433 copies, 272 cmp8 copies54.1433 copies, 272 cmp16 copies27.1433 copies, 272 cmp32 copies13.5433 copies, 272 cmpEnglish-like · pattern of 8copies followed per occurrence32 occurrences
Fig. 6 The chain the search walks per occurrence returned, which falls as the collection grows. A structure reporting only primaries would show a rising line here and a wrong answer set.

Why the primary count is flat and not merely slow-growing

The plate at the top shows a line that does not move, and it is worth checking whether that is a property or a coincidence of this pattern.

The primaries are the occurrences crossing a boundary. When a text is copied, its parse gains one phrase — a single reference covering the whole copy — so the copy contributes no new boundaries inside itself. Every occurrence inside the second copy therefore lies inside one phrase, and is secondary.

That is exact rather than approximate: at two copies, four, eight, sixteen and thirty-two the primary count is one, one, one, one and one, and the secondary count is one, three, seven, fifteen and thirty-one. The pattern is 2k12^k - 1 because each copy’s occurrence is produced from the previous copy’s, which is the chain the propagation queue walks.

Drift breaks it. At one substitution per hundred characters per copy the copies are no longer single phrases, boundaries appear inside them, and some of the occurrences become primary — which is measurable and is what makes the flat line a claim about identical copies rather than about repetitiveness in general.

The occurrences a search finds, and the ones it producesOne 8-character pattern in a collection that grows by repeating itself. The occurrences crossing a phrase boundary — the primaries, which are the only ones the search machinery looks for — stay at 2 however many copies there are. Every other occurrence is inside a phrase, so it is a copy of one already found, and it is produced by adding the phrase's displacement rather than searched for. At 32 copies that is 28 of the 30. Both axes are logarithmic.1,00010,000110occurrences12481632characters in the collection · copies aboveall occurrencessecondaryprimarypattern "ss is un" · z = 4822 primary · 28 secondary
Fig. 7 The same sweep with the copies drifting. Boundaries now appear inside every copy, so the primary count grows and the clean separation blurs.

The two kinds are checked as a definition

The classification is not a label attached by the code that produced each occurrence; it is checked against the parse.

For every occurrence the search stage reports, the check finds the phrase containing its start position and requires the occurrence to run past that phrase’s end — that is, to cross the boundary. For every occurrence the propagation produces, it requires the opposite.

That matters because “primary” and “secondary” are otherwise just names for “found by the first stage” and “found by the second”, which would make the classification true by construction and worth nothing. Checked against the parse, it is a claim: the search stage finds exactly the crossing occurrences and the propagation finds exactly the contained ones.

On a six-character pattern in a collection of six copies the counts are six primary and thirty secondary, all classified correctly. A single misclassification would mean either that the search had missed a crossing occurrence — a correctness bug — or that the propagation had produced one it should have found, which is a slower but still correct structure.

A reduction is not a filter, and the difference is a stage

It is worth being exact about what has been removed, because “search fewer places” describes both this and a filter, and they are not the same construction.

A filter proposes candidate positions and hands them to an expensive stage that decides. The proposal may be wrong; the verification is what makes the answer correct; and the filter’s worth is the ratio between what it proposes and what survives — its selectivity, which is a filter has a selectivity in this collection’s vocabulary and is measured for two filters in this same phase.

The parse’s reduction proposes nothing. An occurrence lying inside a phrase is an occurrence of the pattern, because the phrase’s characters are a copy, so the propagation produces answers rather than candidates. There is no verification step, no false positive and no selectivity to report.

That is a stronger construction and it has a matching cost. A filter’s guarantee is one-sided and cheap to establish — a pigeonhole argument, in the case of the filter that feeds the table. The reduction’s guarantee needs the parse to be exactly what it claims, and a parse with one phrase whose source is wrong produces occurrences that are not occurrences, silently and in quantity.

The occurrences a search finds, and the ones it producesOne 8-character pattern in a collection that grows by repeating itself. The occurrences crossing a phrase boundary — the primaries, which are the only ones the search machinery looks for — stay at 1 however many copies there are. Every other occurrence is inside a phrase, so it is a copy of one already found, and it is produced by adding the phrase's displacement rather than searched for. At 16 copies that is 15 of the 16. Both axes are logarithmic.1,000110occurrences124816characters in the collection · copies aboveall occurrencessecondaryprimarypattern "atatagct" · z = 1481 primary · 15 secondary
Fig. 8 The split on four-symbol text, where phrases are short and more of the answer is primary. The reduction is exact here too and it is reducing much less.

Where the shape comes from in other structures

The split has an analogue in this collection’s other search machinery and it is worth naming, because it is not a coincidence of parses.

The filter that feeds the table splits a pattern into k+1k+1 pieces and searches for each of them exactly, on the grounds that one piece must survive kk errors intact. That is the same move: an argument that reduces a search over many places to a search over few, followed by a cheap stage that recovers the rest.

One pass for every pattern at once does it differently — a single automaton over all patterns, with no reduction at all — and the difference is exactly whether a structural property of the input is available to exploit.

What is unusual here is that the reduction is exact rather than a filter. A pigeonhole filter proposes candidates that must be verified; this proposes nothing and verifies nothing. An occurrence inside a phrase is an occurrence, by the definition of a phrase, and the propagation is arithmetic rather than a check.

What this costs in the worst case

The flat primary count is a property of collections of copies, and the worst case runs the other way.

A text with no repetition at all has zz near n/logσnn/\log_\sigma n and phrases of a few characters each, so almost every occurrence of a pattern longer than a phrase crosses a boundary and almost everything is primary. The propagation then produces nothing and the search does all the work, over zz boundaries which is nearly nn positions.

That is the same degeneration a sampling that costs more than the array records for the run-based structure, arriving at the query rather than at the size. Both structures are built on a measure, and on a text where the measure is Θ(n)\Theta(n) the structure is a slow version of something simpler.

What the two stages do to a query’s shape

Put the pieces together and a query against this structure has a cost with two terms and no dial in it.

The first term is m1m-1 pairs of binary searches over zz boundaries, each comparison costing a chain walk. That term depends on the pattern’s length, on the parse’s size, and on where in the text the binary searches happen to land — and it is paid whether the pattern occurs once or a thousand times.

The second is one range query per occurrence produced. On this implementation that is a linear scan of the phrases whose sources could contain it; on a published one it is a logarithmic range query. Either way it is per answer.

So the structure is at its best on queries with many answers over a collection with few phrases, and at its worst on queries with no answers over a text with many. Both extremes are measured in this strand, and the second is the one a reader is more likely to have.

That shape — a fixed cost for the question and a marginal cost per answer — is the same one an index larger than what it indexes drew for a suffix array and a search that runs backwards drew for a transform. Three structures, three mechanisms, one shape, and the constants are where they differ.

Three compressed indexes, three measures of the same collectionEnglish-like of 512 characters repeated up to 32 times. The entropy-bounded FM-index follows n·H_k and grows with the collection, from 3,511 bits to 34,615; the r-index follows r and grows only in the width of a position; the phrase index follows z and is the smallest of the three at every point past the first, ending at 8,892 bits — 3.89x under the entropy-bounded structure. None of the three holds a text. Both axes are logarithmic.1,00010,00010⁴bits12481632characters in the collection · copies aboveFM-index, compressedr-indexphrase indexEnglish-like · divergence 0z 156 · r 233
Fig. 9 The structures those queries run against. The one this essay is about is the lowest line, and its query is the one with two stages.

The unmeasured case has an arithmetic

The last of the limits below — a pattern whose occurrences all cross boundaries on a collection that repeats — is named as unmeasured, and it can be bounded without measuring it.

On a collection of kk copies of a base of length LL, every copy after the first is one phrase. So a pattern of length mLm \le L lying wholly inside a copy lies inside that phrase and is secondary, always. The only way an occurrence in a later copy can be primary is for it to straddle a junction — the seam where one copy ends and the next begins.

And every junction is the same text: the end of the base followed by its start. So the pattern either straddles a junction or it does not, and the answer is the same at all k1k-1 of them.

The primary count therefore takes exactly two values on a collection of exact copies: one, or kk. Not a smooth rise — a step. The flat line at m=8m = 8 is the first case; the rise at m=12m = 12 is the second, and it rises linearly because a junction match is one match per junction.

That makes the bad case sharp rather than vague. If the pattern straddles the junction, every occurrence is primary, the propagation produces nothing at all, and the search does the whole job over zz boundaries — on a collection whose zz is 156 and whose answer is thirty-two. The structure’s entire advantage is gone on a collection that looks maximally favourable by every measure this field quotes.

How likely is it? A pattern of mm characters drawn from a uniform position straddles a junction at (k1)(m1)(k-1)(m-1) of the kLkL positions, which for large kk is

m1L.\frac{m-1}{L}.

The collection’s size cancels. More copies means more junctions and proportionally more text, so the risk is decided by the pattern length against the base document’s length and by nothing else. At m=8m = 8 on a 512-character base it is 1.4%; at m=64m = 64 it is 12%; at m=128m = 128 it is a quarter.

Which turns the unmeasured case into a stated condition. This structure is safe for patterns short against the repeating unit and degrades as the pattern approaches it — a query for a phrase inside a document is fine, and a query for something as long as the document is where the reduction stops reducing. That is a different axis from the one the phrases a text copies from itself sweeps, which varies the collection while holding the pattern short.

It also explains the drifting sweep without appealing to anything new. One substitution per hundred characters breaks each copy into many phrases, so the junctions stop being the only seams and the two-valued step becomes a spread — which is what that plate shows, and what makes the flat line a claim about identical copies rather than about repetition, exactly as this page says. An index with z in its size prices the structure on the assumption that the flat case is the usual one, and (m1)/L(m-1)/L is how often it is not.

The honest limit

The propagation here is a queue over phrases sorted by source, examined linearly within a source range. A published structure answers the same question with a two-dimensional range query and the counts on these plates are what that query would replace — so the search costs in this essay are upper bounds for a structure nobody here built, and every plate carries the candidate count for that reason.

The classification check runs on one pattern per collection rather than on many, because it requires the parse and the occurrence set together and the exhaustive comparison is quadratic. The counts quoted are therefore exact for the patterns measured and are not a distribution.

What is not here is a pattern longer than a phrase in a repetitive collection — a query where every occurrence crosses a boundary even though the collection repeats. That is the case where this structure’s advantage disappears while the collection still looks favourable, and it is not measured.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

The 8 essays that link to this one and share the most of its objects, of 10 that link here.

The objects this essay names

Each one links to every other essay that touches it.

Index sizeLempel ziv parseLocateMeasurementPattern matchingPhrasePrimary occurrenceR-indexRepetitionSecondary occurrenceSelf-indexString matchingTrade offVerification