API Reference¶
This page is generated automatically from the docstrings in the dafsa package, so it
always matches the installed code.
Finite-state structures for sequence data.
Sets of sequences are stored as automata in which every shared beginning and every
shared ending is held once. The package structure, the API contract, the audit of
1.0 that motivated the 2.0 rewrite, and the decisions taken along the way are in
ARCHITECTURE.md at the root of the repository.
The pieces: the frozen core (Alphabet, Automaton), the semiring layer
(dafsa.semirings), the dictionary structures Trie, Dafsa and
CompactDafsa, the substring indexes SuffixAutomaton and Cdawg, the
counting layer that makes an automaton an index rather than only a set, the
transducers Fst, and dafsa.export.
Examples:
>>> import dafsa
>>> automaton = dafsa.Dafsa.from_sequences(["tap", "taps", "top", "tops"])
>>> "taps" in automaton
True
>>> counted = dafsa.Dafsa.from_sequences(
... ["tip", "tip", "tap"], semiring=dafsa.semirings.COUNTING
... )
>>> counted.weight("tip")
2
The automaton is also an index over its own language:
>>> len(automaton), automaton.unrank(0), automaton.rank(("t", "o", "p", "s"))
(4, ('t', 'a', 'p'), 3)
Alphabet ¶
An ordered, immutable vocabulary of tokens.
Symbols are assigned by position: the token at index i of
tokens has symbol i. The constructor preserves the order it is
given; use from_sequences to derive a canonically ordered alphabet
from data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tokens
|
Iterable[Token]
|
The tokens, in the order symbols should be assigned. Repeats are ignored, keeping the first occurrence. |
required |
Examples:
>>> alphabet = Alphabet("abc")
>>> alphabet.id("b")
1
>>> alphabet.encode("cab")
(2, 0, 1)
>>> alphabet.decode((2, 0, 1))
('c', 'a', 'b')
from_sequences
classmethod
¶
Build an alphabet from the tokens appearing in sequences.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequences
|
Iterable[Sequence[Token]]
|
The sequences whose tokens make up the vocabulary. |
required |
Returns:
| Type | Description |
|---|---|
Alphabet
|
An alphabet over every distinct token seen, in sorted order where |
Alphabet
|
the tokens are mutually comparable and in first-encountered order |
Alphabet
|
otherwise. |
Examples:
id ¶
Return the symbol for token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
Token
|
The token to look up. |
required |
Returns:
| Type | Description |
|---|---|
Symbol
|
The symbol assigned to |
Raises:
| Type | Description |
|---|---|
UnknownTokenError
|
If |
token ¶
Return the token for symbol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbol
|
Symbol
|
The symbol to look up. |
required |
Returns:
| Type | Description |
|---|---|
Token
|
The token assigned to |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
encode ¶
Encode a sequence of tokens as symbols.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to encode. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Symbol, ...]
|
The encoded sequence. |
Raises:
| Type | Description |
|---|---|
UnknownTokenError
|
If any token is not in the alphabet. Use |
try_encode ¶
Encode a sequence of tokens, or report that it cannot be encoded.
This is what membership tests use: a sequence containing a token the automaton has never seen is simply not accepted, which is an answer rather than an error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to encode. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Symbol, ...] | None
|
The encoded sequence, or |
decode ¶
Decode symbols back into tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbols
|
Iterable[Symbol]
|
The symbols to decode. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Token, ...]
|
The decoded tokens. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If any symbol is out of range. |
Automaton ¶
Automaton(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
An immutable, deterministic, acyclic automaton in CSR form.
Instances are produced by a builder's freeze() and are not meant to be
constructed directly: the constructor trusts its arguments, because the
builder is what establishes the invariants they have to satisfy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alphabet
|
Alphabet
|
The alphabet the symbols refer to. |
required |
first
|
array[int]
|
Offsets into the transition arrays, of length |
required |
symbol
|
array[int]
|
Transition symbols, ascending within each state's slice. |
required |
target
|
array[int]
|
Transition targets, parallel to |
required |
flags
|
array[int]
|
Per-state flag bits, one entry per state. |
required |
semiring
|
Semiring[Any]
|
The semiring the weights belong to. |
BOOLEAN
|
transition_weights
|
list[Any] | None
|
One weight per transition, or |
None
|
final_weights
|
list[Any] | None
|
One weight per state, or |
None
|
labels
|
list[tuple[Symbol, ...]] | None
|
One compound label per transition, or |
None
|
initial_weight
|
Any
|
A weight applied to every accepted sequence. |
None
|
Notes
The invariants the constructor assumes, all established by freeze():
first is non-decreasing and starts at zero; symbol is strictly
ascending within each state's slice, which is what makes the binary search
in step correct and encodes determinism; every state is reachable
from ROOT; and the transitions contain no cycle.
The two weight arrays are None in the common unweighted case rather than
filled with copies of one. A plain acceptor therefore costs nothing for
weights it does not use, which matters because memory frugality is much of
the point of this representation. labels follows the same rule.
Compound labels do not change the CSR layout. symbol continues to hold
one symbol per transition — the first of its label — so determinism,
ascending order within a state, and the binary search in step all
work exactly as before. What changes is only how many tokens a transition
consumes.
initial_weight
property
¶
The weight every accepted sequence carries before its path.
is_weighted
property
¶
Whether any weight differs from the semiring's one.
False means the automaton is a plain acceptor and stores no weight
arrays; weight still answers, with one for accepted
sequences.
states ¶
Return the states, as a range over their dense ids.
Returns:
| Type | Description |
|---|---|
range
|
|
is_final ¶
Return whether state is accepting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to inspect. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether a sequence ending at |
out_degree ¶
Return the number of transitions leaving state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to inspect. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of outgoing transitions. |
final_weight ¶
Return the weight contributed by accepting at state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to inspect. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The state's final weight, or the semiring's |
Any
|
not accepting. |
transition_weight ¶
Return the weight of the transition at index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
A transition index, as produced by |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The transition's weight, or the semiring's |
Any
|
stores no transition weights. |
transition_indices ¶
Return the indices of state's transitions, in symbol order.
The index-based accessors below exist so that the dynamic programs can
walk the arrays without allocating a Transition per step.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state whose transitions to index. |
required |
Returns:
| Type | Description |
|---|---|
range
|
Indices into the transition arrays. |
transition_symbol ¶
Return the first symbol consumed by the transition at index.
transition_target ¶
Return the state entered by the transition at index.
transition_label ¶
Return every symbol consumed by the transition at index.
One symbol for an ordinary transition, several for a compacted one.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
A transition index. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Symbol, ...]
|
The transition's label. |
transition_tokens ¶
Return the transition's label as caller-facing tokens.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
A transition index. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Token, ...]
|
The tokens the transition consumes. |
transitions ¶
Iterate over the transitions leaving state, in symbol order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state whose transitions to yield. |
required |
Yields:
| Type | Description |
|---|---|
Transition
|
Each outgoing transition, carrying its weight. |
all_transitions ¶
Iterate over every transition, ordered by source then symbol.
Yields:
| Type | Description |
|---|---|
Transition
|
Each transition in the automaton. |
transition_index ¶
Return the index of state's transition on symbol.
The binary search is over one state's slice of the symbol array, which is
ascending — the invariant that makes this correct is established by
freeze().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to leave. |
required |
symbol
|
Symbol
|
The symbol to consume. |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
The transition's index into the transition arrays, or |
int | None
|
there is no such transition. |
step ¶
Follow one transition.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to leave. |
required |
symbol
|
Symbol
|
The symbol to consume. |
required |
Returns:
| Type | Description |
|---|---|
State | None
|
The state reached, or |
State | None
|
|
walk ¶
Follow a sequence of symbols.
The traversal is a loop, not a recursion, so sequence length is bounded by memory rather than by the interpreter's stack.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
symbols
|
Iterable[Symbol]
|
The symbols to consume in order. |
required |
start
|
State
|
The state to start from. Defaults to |
ROOT
|
Returns:
| Type | Description |
|---|---|
State | None
|
The state reached after consuming every symbol, or |
State | None
|
path leaves the automaton partway through. |
accepts ¶
Return whether sequence is accepted.
A sequence containing a token outside the alphabet is not accepted; that is an answer, not an error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to test. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether the automaton accepts |
weight ¶
Return the weight sequence is accepted with.
The weight is the semiring product of the transition weights along the path and the final weight of the state it ends at — which, for an automaton built from weighted sequences, is exactly the weight that sequence was inserted with.
This is the query 1.0's lookup() was reaching for and got wrong:
there, weights were counters over a minimized graph, so the returned
"cumulative weight" summed the frequencies of every sequence sharing an
edge. DAFSA(["dib", "tip", "tips", "top"]).lookup("tip") gave 7
for a sequence inserted once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to weigh. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The sequence's weight, or the semiring's |
Any
|
accepted. |
Any
|
rejection needs no special case at the call site. |
match ¶
Return the full path sequence takes, or None if it is rejected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to match. |
required |
Returns:
| Type | Description |
|---|---|
Match | None
|
The states visited, the transitions taken, and the weight. |
Examples:
paths ¶
Yield every accepting path for sequence.
A deterministic acceptor has at most one, so this yields nothing or one
Match and match is the direct way to ask. It exists so
that code written against the transducers — where a single input can have
several analyses — reads the same against an acceptor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to match. |
required |
Yields:
| Type | Description |
|---|---|
Match
|
Each accepting path. |
longest_prefix_of ¶
Return the longest accepted prefix of sequence.
Useful for segmentation: repeatedly taking the longest accepted prefix of what remains is greedy longest-match tokenisation against a lexicon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The tokens to search within. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Token, ...] | None
|
The longest prefix of |
tuple[Token, ...] | None
|
|
tuple[Token, ...] | None
|
when the root is accepting. |
Examples:
starts_with ¶
Yield the accepted sequences beginning with prefix, in order.
Costs the size of the answer, not the size of the language: the prefix is followed once to reach a state, and only that state's subtree is walked. This is the query an autocomplete is made of.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
Sequence[Token]
|
The tokens every yielded sequence must start with. |
required |
Yields:
| Type | Description |
|---|---|
tuple[Token, ...]
|
Each accepted sequence extending |
Examples:
rank ¶
Return the position of sequence in iteration order.
Together with unrank this makes the automaton a minimal perfect
hash over its own language: every accepted sequence maps to a distinct
integer in range(len(automaton)), and back.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
An accepted sequence. |
required |
Returns:
| Type | Description |
|---|---|
int
|
Its zero-based position in |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the sequence is not accepted. |
Examples:
unrank ¶
Return the accepted sequence at position.
The inverse of rank, and cheaper than it looks: whole subtrees are
skipped by their known sizes, so the cost tracks the sequence's length
rather than its position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position
|
int
|
A zero-based position in |
required |
Returns:
| Type | Description |
|---|---|
tuple[Token, ...]
|
The sequence at that position. |
Raises:
| Type | Description |
|---|---|
IndexError
|
If |
Examples:
suffix_count ¶
Return how many sequences are accepted from state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
State
|
The state to count from. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The size of the state's right language. |
topological_order ¶
Return the states with every state before all of its successors.
Exposed because it is the order any dynamic program over the structure needs, and because the canonical breadth-first numbering is not topological — a fact easy to assume otherwise and get wrong.
Returns:
| Type | Description |
|---|---|
list[State]
|
The states, sources before targets. |
total_weight ¶
Return the semiring sum of every accepted sequence's weight.
For COUNTING this is the total number of
insertions, as distinct from len(), which is the number of distinct
sequences.
Returns:
| Type | Description |
|---|---|
Any
|
The total, or the semiring's |
Examples:
push ¶
Return an equivalent automaton with its weight moved towards the front.
Every accepted sequence keeps exactly the weight it had. What changes is
where along its path that weight sits: afterwards, the weights leaving any
state combine with its final weight to the semiring's one, so a
prefix's weight already tells you as much as it can about what follows.
Returns:
| Type | Description |
|---|---|
Any
|
A new automaton of the same class. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the semiring is not divisible, or if its multiplication does not commute. |
ZeroDivisionError
|
If some state cannot reach an accepting state, since its potential is
then the semiring's |
k_best ¶
Return the k best accepted sequences with their weights.
Only meaningful when the semiring is idempotent, so that plus selects
a better weight instead of accumulating.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
int
|
How many to return. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[tuple[Token, ...], Any]]
|
Up to |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the semiring is not idempotent. |
Examples:
Match ¶
Bases: NamedTuple
Everything known about one accepted sequence.
Returned by Automaton.match. This is the resolution of issue #8: 1.0's
lookup() returned only the final node and an uninterpretable cumulative
weight, so the path a sequence took was not recoverable without dropping to a
graph library.
Transition ¶
Bases: NamedTuple
One outgoing transition of a state.
weight is typed Any because it belongs to whichever
semiring the automaton was built over, and the automaton does not carry that
type as a parameter. For an unweighted automaton it is the semiring's
one.
AcyclicityError ¶
Bases: DafsaError, ValueError
The transitions being frozen contain a cycle.
Every structure in this library is acyclic; the algorithms that traverse a
frozen automaton rely on it, so the cycle is rejected at freeze() rather
than allowed to cause a non-terminating traversal later.
DafsaError ¶
Bases: Exception
Base class for every error raised by this library.
DeterminismError ¶
Bases: DafsaError, ValueError
A state was given two outgoing transitions on the same symbol.
The structures in this library are deterministic by construction, so this signals a defect in whatever drove the builder.
ExportError ¶
Bases: DafsaError, RuntimeError
An export could not be produced.
Raised when an external tool an export depends on is missing or fails — Graphviz, in practice. The automaton itself is fine; only the rendering of it could not be made.
UnknownTokenError ¶
Bases: DafsaError, KeyError
A token is not present in the alphabet.
Raised when encoding a sequence that contains a token the alphabet was not built from. Membership tests do not raise this: an unknown token simply means the sequence cannot be accepted.
Fst ¶
Fst(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: Automaton
An acyclic weighted transducer over (input, output) pairs.
Examples:
>>> fst = Fst.from_pairs([("cat", "chat"), ("dog", "chien")])
>>> fst.apply("cat")
[('c', 'h', 'a', 't')]
>>> fst.apply("cow")
[]
Ambiguity is allowed, and reported as several results:
>>> ambiguous = Fst.from_alignments(
... [[("a", "x")], [("a", "y")]]
... )
>>> sorted(ambiguous.apply("a"))
[('x',), ('y',)]
from_alignments
classmethod
¶
Build a transducer from aligned pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alignments
|
Iterable[Sequence[Pair]]
|
Each alignment is a sequence of |
required |
semiring
|
Semiring[Any]
|
The semiring weights belong to. |
BOOLEAN
|
Returns:
| Type | Description |
|---|---|
Fst
|
The frozen, minimal transducer. |
from_weighted_alignments
classmethod
¶
from_weighted_alignments(pairs: Iterable[tuple[Sequence[Pair], Any]], *, semiring: Semiring[Any]) -> Fst
Build a transducer from weighted aligned pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pairs
|
Iterable[tuple[Sequence[Pair], Any]]
|
|
required |
semiring
|
Semiring[Any]
|
The semiring the weights belong to. |
required |
Returns:
| Type | Description |
|---|---|
Fst
|
The frozen, minimal transducer. |
from_pairs
classmethod
¶
from_pairs(pairs: Iterable[tuple[Sequence[Token], Sequence[Token]]], *, semiring: Semiring[Any] = BOOLEAN) -> Fst
Build a transducer from unaligned sequence pairs.
Convenience only. The two sides are zipped position by position and the
shorter one padded with EPSILON at the end. That is an
alignment, not the right one for any particular linguistic purpose — for
that, align deliberately and use from_alignments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pairs
|
Iterable[tuple[Sequence[Token], Sequence[Token]]]
|
|
required |
semiring
|
Semiring[Any]
|
The semiring weights belong to. |
BOOLEAN
|
Returns:
| Type | Description |
|---|---|
Fst
|
The frozen, minimal transducer. |
apply ¶
Return every output the transducer relates to sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The input tokens. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[Token, ...]]
|
Each output, in the alphabet's order. Empty when the input is not |
list[tuple[Token, ...]]
|
related to anything, several when the transducer is ambiguous. |
project ¶
Return the acceptor for one side of the relation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
side
|
str
|
|
'input'
|
Returns:
| Type | Description |
|---|---|
Dafsa
|
The minimal acceptor for the projected sequences, with epsilons |
Dafsa
|
removed. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
The projection is unweighted. Projecting collapses the distinction between paths that shared a side, and reconciling their weights is weighted determinization — a genuinely different algorithm, and out of scope here. Dropping the weights is the honest option; carrying one arbitrary path's weight would not be.
Semiring ¶
Bases: Protocol[W]
The interface every semiring satisfies.
This is a Protocol, so any object with these members works —
a caller's own semiring needs no inheritance and no registration. The
built-ins below are annotated as Semiring[...] at the point they are
created, which makes conformance a type error rather than a runtime surprise.
Attributes:
| Name | Type | Description |
|---|---|---|
zero |
W
|
Additive identity and multiplicative annihilator. Conventionally the weight of a path that does not exist. |
one |
W
|
Multiplicative identity. The weight of the empty path. |
idempotent |
bool
|
Whether |
commutative |
bool
|
Whether |
divisible |
bool
|
Whether |
key ¶
Return a hashable canonical form of weight.
Used as part of a state's signature during minimization. It exists so that a semiring whose weights are unhashable, or which represents one value in several ways, can still be minimized.
divide ¶
Return the w with times(w, right) == left.
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |
CompactDafsa ¶
CompactDafsa(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: _Structure
A path-compacted automaton: transitions may consume several tokens.
Produced by _Structure.compact. Where an ordinary automaton spends a
state on each token of a forced chain, this spends one transition on the
whole chain, which is what makes a drawn automaton readable at any size.
The token contract is unchanged. Membership, weights, ranking and iteration
all still take and return sequences of the original tokens; the compound
labels are an internal matter, visible through
transition_tokens when a renderer needs them.
Examples:
>>> compacted = Dafsa.from_sequences(["tapas", "topos"]).compact()
>>> "tapas" in compacted, "tapa" in compacted
(True, False)
>>> sorted("".join(str(t) for t in s) for s in compacted)
['tapas', 'topos']
The single t transition out of the root now carries the whole forced
prefix, and 1.0 raised IndexError on exactly this input:
Dafsa ¶
Dafsa(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: _Structure
A minimal deterministic acyclic finite-state automaton.
Equivalent states are shared, so the structure is as small as a deterministic automaton for this language can be. That sharing is what makes state ids meaningless as identifiers — a suffix state is reached by every prefix that leads to it — and it is why the counting layer, rather than the states themselves, is what answers questions about individual sequences.
Examples:
>>> dafsa = Dafsa.from_sequences(["tap", "taps", "top", "tops"])
>>> "tops" in dafsa, "to" in dafsa
(True, False)
The shared ps suffix is stored once, where a trie would store it twice:
>>> words = ["tap", "taps", "top", "tops"]
>>> dafsa.num_states < Trie.from_sequences(words).num_states
True
Weights mean what they say, which is the thing 1.0 could not do:
>>> from dafsa.semirings import COUNTING
>>> counted = Dafsa.from_sequences(["tip", "tip", "tap"], semiring=COUNTING)
>>> counted.weight("tip"), counted.weight("tap"), counted.weight("nope")
(2, 1, 0)
from_sequences
classmethod
¶
Build the minimal automaton accepting exactly sequences.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequences
|
Iterable[Sequence[Token]]
|
The sequences to accept. Each is a sequence of hashable tokens; a
|
required |
semiring
|
Semiring[Any]
|
The semiring weights belong to. With the default, the result is a
plain acceptor. With |
BOOLEAN
|
Returns:
| Type | Description |
|---|---|
Dafsa
|
The frozen, minimal automaton. |
from_weighted
classmethod
¶
Build the minimal automaton from (sequence, weight) pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pairs
|
Iterable[tuple[Sequence[Token], Any]]
|
The sequences and their weights. Repeated sequences have their
weights combined with the semiring's |
required |
semiring
|
Semiring[Any]
|
The semiring the weights belong to. |
required |
Returns:
| Type | Description |
|---|---|
Dafsa
|
The frozen, minimal automaton. Minimization is weight-aware: two |
Dafsa
|
states are shared only if their weights agree as well as their |
Dafsa
|
transitions, so |
Dafsa
|
with. That is stricter than unweighted minimization and yields more |
Dafsa
|
states — the price of weights that mean something. |
Trie ¶
Trie(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: _Structure
A prefix tree: shared prefixes, no shared suffixes.
A trie is the honest baseline for the comparison this library was originally built to illustrate, and it remains useful whenever states need to be distinguishable by the prefix that reaches them — which minimization destroys, since a shared suffix state is reached by many prefixes.
Construction costs the same as Dafsa minus the register lookups, so
a trie is never the cheaper choice; it is the choice that keeps prefixes
distinct.
Examples:
>>> trie = Trie.from_sequences(["tap", "taps", "top"])
>>> "taps" in trie, "ta" in trie
(True, False)
One state per distinct prefix — t, ta, tap, taps, to,
top — plus the root:
from_sequences
classmethod
¶
Build a trie accepting exactly sequences.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequences
|
Iterable[Sequence[Token]]
|
The sequences to accept. Each is a sequence of hashable tokens; a
|
required |
semiring
|
Semiring[Any]
|
The semiring weights belong to. With the default, the result is a
plain acceptor. With |
BOOLEAN
|
Returns:
| Type | Description |
|---|---|
Trie
|
The frozen trie. |
from_weighted
classmethod
¶
Build a trie from (sequence, weight) pairs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pairs
|
Iterable[tuple[Sequence[Token], Any]]
|
The sequences and their weights. Repeated sequences have their
weights combined with the semiring's |
required |
semiring
|
Semiring[Any]
|
The semiring the weights belong to. |
required |
Returns:
| Type | Description |
|---|---|
Trie
|
The frozen trie. |
Cdawg ¶
Cdawg(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: _SubstringIndex
A compact directed acyclic word graph: a path-compacted suffix automaton.
Produced by SuffixAutomaton.compact. Chains of states that every
occurrence is forced through collapse into single transitions labelled with
several tokens, which is what makes the structure small enough to draw and
cheap enough to store for a long text.
Every query still takes and returns ordinary token sequences. A substring may now end part-way along a label, at a position with no state of its own; the queries account for that.
Examples:
>>> index = SuffixAutomaton.from_sequence("banana")
>>> compacted = index.compact()
>>> compacted.num_states < index.num_states
True
>>> compacted.contains_substring("nan"), compacted.contains_substring("nab")
(True, False)
>>> compacted.num_substrings() == index.num_substrings()
True
SuffixAutomaton ¶
SuffixAutomaton(alphabet: Alphabet, first: array[int], symbol: array[int], target: array[int], flags: array[int], semiring: Semiring[Any] = BOOLEAN, transition_weights: list[Any] | None = None, final_weights: list[Any] | None = None, labels: list[tuple[Symbol, ...]] | None = None, initial_weight: Any = None)
Bases: _SubstringIndex
The minimal automaton accepting every suffix of one sequence.
Accepting states mark the suffixes. Every substring is walkable from the
root whether or not it ends at an accepting state, which is why
contains_substring ignores acceptance and in does not.
Examples:
>>> index = SuffixAutomaton.from_sequence("banana")
>>> sorted("".join(s) for s in index)
['', 'a', 'ana', 'anana', 'banana', 'na', 'nana']
Every suffix is accepted; a substring merely occurs. nan occurs inside
banana but is not a suffix of it, and the two queries differ:
>>> "nan" in index, index.contains_substring("nan")
(False, True)
>>> "nana" in index, index.contains_substring("nana")
(True, True)
>>> index.contains_substring("bana"), index.contains_substring("nab")
(True, False)
from_sequence
classmethod
¶
Build the suffix automaton of sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sequence
|
Sequence[Token]
|
The sequence to index. A |
required |
semiring
|
Semiring[Any]
|
The semiring weights belong to. Every weight is |
BOOLEAN
|
Returns:
| Type | Description |
|---|---|
SuffixAutomaton
|
The frozen index. |
tokenize ¶
Split text into tokens.
The library's input contract is a sequence of tokens, and a str is a
sequence of single characters. Multi-character tokens therefore need an
explicit split, and this is it.
That explicitness is the fix for issue #17. 1.0 took a delimiter
parameter that was never used to split anything — it only joined labels when
compacting — so DAFSA(["a b c"], delimiter=" ") silently treated the
spaces as tokens. The command-line tool split on whitespace before calling
the library, which is why the CLI appeared to work while the API did not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to split. |
required |
sep
|
str | None
|
The separator. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[str, ...]
|
The tokens. |
Examples:
Without it, a string is characters — which is a reasonable default, and now a visible choice rather than a surprise:
compose ¶
Return the transducer relating left's input to right's output.
The standard product construction: a state of the result is a state of each
input plus a filter marker, and a transition matches left's output
against right's input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
left
|
Fst
|
The transducer applied first. Its output alphabet is matched against
|
required |
right
|
Fst
|
The transducer applied second. |
required |
semiring
|
Semiring[Any] | None
|
The semiring of the result. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Fst
|
The composed transducer. |
Raises:
| Type | Description |
|---|---|
DeterminismError
|
If the composition is ambiguous — if two composed paths carry the same
|
Notes
Epsilons are filtered after Mohri, Pereira and Riley: without a filter, a
composition where left deletes and right inserts produces the same
path by several interleavings.
Examples: