Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Suffix_Structures.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
48#ifndef SUFFIX_STRUCTURES_H
49#define SUFFIX_STRUCTURES_H
50
51#include <algorithm>
52#include <limits>
53#include <string>
54#include <string_view>
55#include <utility>
56#include <array>
57
58#include <ah-errors.H>
59#include <tpl_array.H>
60#include <tpl_sort_utils.H>
61
62namespace Aleph {
63namespace suffix_structures_detail {
68inline Array<size_t> all_positions(const size_t n)
69{
71 out.reserve(n + 1);
72 for (size_t i = 0; i <= n; ++i)
73 out.append(i);
74 return out;
75}
76
81{
82 introsort(a);
83}
84} // namespace suffix_structures_detail
85
98inline Array<size_t> suffix_array(const std::string_view text)
99{
100 const size_t n = text.size();
101 if (n == 0)
102 return {};
103
108
109 for (size_t i = 0; i < n; ++i)
110 {
111 sa[i] = i;
112 rank[i] = static_cast<unsigned char>(text[i]);
113 }
114
115 int max_rank = 255; // initial ranks are char codes in [0, 255]
116
117 for (size_t k = 1;;)
118 {
119 auto key2 = [&](const size_t idx) -> int
120 {
121 return (idx + k < n) ? rank[idx + k] : -1;
122 };
123
124 auto key1 = [&](const size_t idx) -> int
125 {
126 return rank[idx];
127 };
128
131
132 auto less_idx = [&](const size_t a, const size_t b)
133 {
134 if (rank[a] != rank[b])
135 return rank[a] < rank[b];
136 const int ra = (a + k < n) ? rank[a + k] : -1;
137 const int rb = (b + k < n) ? rank[b + k] : -1;
138 return ra < rb;
139 };
140
141 tmp_rank[sa[0]] = 0;
142 for (size_t i = 1; i < n; ++i)
143 tmp_rank[sa[i]] = tmp_rank[sa[i - 1]] + (less_idx(sa[i - 1], sa[i]) ? 1 : 0);
144
145 for (size_t i = 0; i < n; ++i)
146 rank[i] = tmp_rank[i];
147
148 max_rank = rank[sa[n - 1]];
149
150 if (max_rank == static_cast<int>(n - 1))
151 break;
152
153 if (k >= n)
154 break;
155
156 if (k > n / 2)
157 break;
158
159 k *= 2;
160 }
161
162 return sa;
163}
164
183inline Array<size_t> lcp_array_kasai(const std::string_view text, const Array<size_t> &sa)
184{
185 const size_t n = text.size();
186
187 ah_domain_error_if(sa.size() != n) << "lcp_array_kasai(): suffix array size must match text size";
188
190 if (n == 0)
191 return lcp;
192
194 for (size_t i = 0; i < n; ++i)
195 lcp[i] = 0;
196
197 Array<size_t> rank(n, std::numeric_limits<size_t>::max());
198 for (size_t i = 0; i < n; ++i)
199 {
200 ah_out_of_range_error_if(sa[i] >= n)
201 << "lcp_array_kasai(): suffix array contains invalid index";
202 ah_out_of_range_error_if(rank[sa[i]] != std::numeric_limits<size_t>::max())
203 << "lcp_array_kasai(): suffix array contains duplicate index";
204 rank[sa[i]] = i;
205 }
206
207 size_t k = 0;
208 for (size_t i = 0; i < n; ++i)
209 {
210 const size_t r = rank[i];
211 if (r + 1 >= n)
212 {
213 k = 0;
214 continue;
215 }
216
217 const size_t j = sa[r + 1];
218 while (i + k < n and j + k < n and text[i + k] == text[j + k])
219 ++k;
220
221 lcp[r] = k;
222
223 if (k > 0)
224 --k;
225 }
226
227 return lcp;
228}
229
243{
244public:
245 static constexpr size_t npos = std::numeric_limits<size_t>::max();
246
251 struct Node
252 {
253 size_t start = 0;
254 size_t end = 0;
255 size_t parent = npos;
258 };
259
260private:
261 std::string text_;
262 size_t original_size_ = 0;
264
265 static char choose_terminal(const std::string_view s)
266 {
267 std::array<bool, 256> used;
268 used.fill(false);
269 for (const unsigned char c : s)
270 used[c] = true;
271
272 for (size_t i = 0; i < 256; ++i)
273 if (not used[i])
274 return static_cast<char>(i);
275
276 ah_domain_error() << "Naive_Suffix_Tree::choose_terminal(): all 256 byte values "
277 << "are present in the input; no terminal marker available";
278
279 return '\0'; // unreachable
280 }
281
282 size_t create_node(const size_t start, const size_t end, const size_t parent, const size_t suffix_index)
283 {
284 Node n;
285 n.start = start;
286 n.end = end;
287 n.parent = parent;
288 n.suffix_index = suffix_index;
289 nodes_.append(n);
290 return nodes_.size() - 1;
291 }
292
293 size_t find_child_by_first_char(const size_t node, const char c) const
294 {
295 const Array<size_t> &children = nodes_[node].children;
296 for (size_t i = 0; i < children.size(); ++i)
297 if (const size_t child = children[i]; text_[nodes_[child].start] == c)
298 return child;
299
300 return npos;
301 }
302
303 void replace_child(const size_t parent, const size_t old_child, const size_t new_child)
304 {
305 for (Array<size_t> &children = nodes_[parent].children; size_t & i : children)
306 if (i == old_child)
307 {
308 i = new_child;
309 return;
310 }
311 }
312
313 void insert_suffix(const size_t suffix_start)
314 {
315 size_t current = 0;
316 size_t pos = suffix_start;
317
318 while (pos < text_.size())
319 {
320 const size_t child = find_child_by_first_char(current, text_[pos]);
321 if (child == npos)
322 {
323 const size_t leaf = create_node(pos, text_.size(), current, suffix_start);
324 nodes_[current].children.append(leaf);
325 return;
326 }
327
328 const size_t edge_start = nodes_[child].start;
329 const size_t edge_end = nodes_[child].end;
330
331 size_t k = 0;
332 while (pos + k < text_.size() and edge_start + k < edge_end
333 and text_[pos + k] == text_[edge_start + k])
334 ++k;
335
336 if (edge_start + k == edge_end)
337 {
338 current = child;
339 pos += k;
340 continue;
341 }
342
343 const size_t split = create_node(edge_start, edge_start + k, current, npos);
344
345 replace_child(current, child, split);
346
347 nodes_[child].start = edge_start + k;
348 nodes_[child].parent = split;
349 nodes_[split].children.append(child);
350
351 const size_t leaf = create_node(pos + k, text_.size(), split, suffix_start);
352 nodes_[split].children.append(leaf);
353
354 return;
355 }
356 }
357
358 void collect_leaf_suffixes(const size_t node, Array<size_t> &out) const
359 {
360 Array<size_t> stack;
361 stack.append(node);
362
363 while (not stack.is_empty())
364 {
365 const size_t u = stack.remove_last();
366 if (nodes_[u].suffix_index != npos)
367 {
368 if (nodes_[u].suffix_index < original_size_)
369 out.append(nodes_[u].suffix_index);
370 continue;
371 }
372
373 for (const Array<size_t> &children = nodes_[u].children; size_t i : children)
374 stack.append(i);
375 }
376 }
377
378public:
387 explicit Naive_Suffix_Tree(const std::string_view text = "")
388 {
389 build(text);
390 }
391
399 void build(const std::string_view text)
400 {
401 const char terminal = choose_terminal(text);
402 std::string new_text(text.begin(), text.end());
403 new_text.push_back(terminal);
404
405 original_size_ = text.size();
406 text_ = std::move(new_text);
407
408 nodes_.empty();
409 nodes_.append(Node{}); // root
410
411 for (size_t i = 0; i < text_.size(); ++i)
412 insert_suffix(i);
413 }
414
423 [[nodiscard]] bool contains(const std::string_view pattern) const
424 {
425 if (pattern.empty())
426 return true;
427
428 size_t current = 0;
429 size_t pos = 0;
430
431 while (pos < pattern.size())
432 {
433 const size_t child = find_child_by_first_char(current, pattern[pos]);
434 if (child == npos)
435 return false;
436
437 const size_t edge_start = nodes_[child].start;
438 const size_t edge_end = nodes_[child].end;
439
440 size_t k = 0;
441 while (pos + k < pattern.size() and edge_start + k < edge_end
442 and text_[edge_start + k] != text_.back() // Never match sentinel
443 and pattern[pos + k] == text_[edge_start + k])
444 ++k;
445
446 if (pos + k == pattern.size())
447 return true;
448
449 if (edge_start + k == edge_end)
450 {
451 current = child;
452 pos += k;
453 continue;
454 }
455
456 return false;
457 }
458
459 return true;
460 }
461
474 [[nodiscard]] Array<size_t> find_all(const std::string_view pattern) const
475 {
476 if (pattern.empty())
478
479 size_t current = 0;
480 size_t pos = 0;
481
482 while (pos < pattern.size())
483 {
484 const size_t child = find_child_by_first_char(current, pattern[pos]);
485 if (child == npos)
486 return {};
487
488 const size_t edge_start = nodes_[child].start;
489 const size_t edge_end = nodes_[child].end;
490
491 size_t k = 0;
492 while (pos + k < pattern.size() and edge_start + k < edge_end
493 and text_[edge_start + k] != text_.back() // Never match sentinel
494 and pattern[pos + k] == text_[edge_start + k])
495 ++k;
496
497 if (pos + k == pattern.size())
498 {
502 return matches;
503 }
504
505 if (edge_start + k == edge_end)
506 {
507 current = child;
508 pos += k;
509 continue;
510 }
511
512 return {};
513 }
514
515 return {};
516 }
517
525 {
526 return nodes_.size();
527 }
528
537 {
538 return original_size_;
539 }
540
548 {
549 return nodes_;
550 }
551};
552
559{
560public:
562 struct State
563 {
564 std::array<int, 256> next;
565 int link = -1;
566 size_t len = 0;
567 size_t first_pos = 0;
568 bool terminal = false;
571 {
572 next.fill(-1);
573 }
574 };
575
576private:
578 int last_ = 0;
579
584 {
585 int p = last_;
586 while (p != -1)
587 {
588 states_[static_cast<size_t>(p)].terminal = true;
589 p = states_[static_cast<size_t>(p)].link;
590 }
591 }
592
593public:
600 {
601 clear();
602 }
603
609 void clear()
610 {
611 states_.empty();
612 states_.append(State{});
613 states_[0].link = -1;
614 states_[0].len = 0;
615 states_[0].first_pos = 0;
616 states_[0].terminal = false;
617 last_ = 0;
618 }
619
626 void extend(const char ch)
627 {
628 const auto c = static_cast<unsigned char>(ch);
629
630 const int cur = static_cast<int>(states_.size());
631 states_.append(State{});
632 states_[static_cast<size_t>(cur)].len = states_[static_cast<size_t>(last_)].len + 1;
633 states_[static_cast<size_t>(cur)].first_pos = states_[static_cast<size_t>(cur)].len - 1;
634
635 int p = last_;
636 while (p != -1 and states_[static_cast<size_t>(p)].next[c] == -1)
637 {
638 states_[static_cast<size_t>(p)].next[c] = cur;
639 p = states_[static_cast<size_t>(p)].link;
640 }
641
642 if (p == -1)
643 states_[static_cast<size_t>(cur)].link = 0;
644 else
645 {
646 if (const int q = states_[static_cast<size_t>(p)].next[c];
647 states_[static_cast<size_t>(p)].len + 1 == states_[static_cast<size_t>(q)].len)
648 states_[static_cast<size_t>(cur)].link = q;
649 else
650 {
651 const int clone = static_cast<int>(states_.size());
652 // Copy q's state before append: append may reallocate states_,
653 // invalidating any reference into the old buffer.
654 const State clone_state = states_[static_cast<size_t>(q)];
655 states_.append(clone_state);
656 states_[static_cast<size_t>(clone)].len = states_[static_cast<size_t>(p)].len + 1;
657 states_[static_cast<size_t>(clone)].terminal = false;
658 // Enforce invariant: only root (state 0) may have link == -1
659 if (states_[static_cast<size_t>(clone)].link == -1)
660 states_[static_cast<size_t>(clone)].link = 0;
661
662 while (p != -1 and states_[static_cast<size_t>(p)].next[c] == q)
663 {
664 states_[static_cast<size_t>(p)].next[c] = clone;
665 p = states_[static_cast<size_t>(p)].link;
666 }
667
668 states_[static_cast<size_t>(q)].link = clone;
669 states_[static_cast<size_t>(cur)].link = clone;
670 }
671 }
672
673 last_ = cur;
674 }
675
682 void build(const std::string_view text)
683 {
684 clear();
685 for (const char c : text)
686 extend(c);
687
689 }
690
698 [[nodiscard]] bool contains(const std::string_view pattern) const
699 {
700 int state = 0;
701 for (const unsigned char c : pattern)
702 {
703 state = states_[static_cast<size_t>(state)].next[c];
704 if (state == -1)
705 return false;
706 }
707
708 return true;
709 }
710
718 {
719 size_t total = 0;
720 for (size_t i = 1; i < states_.size(); ++i)
721 {
722 const State &s = states_[i];
723 const State &parent = states_[static_cast<size_t>(s.link)];
724 total += s.len - parent.len;
725 }
726
727 return total;
728 }
729
738 [[nodiscard]] std::string longest_common_substring(const std::string_view other) const
739 {
740 int state = 0;
741 size_t len = 0;
742
743 size_t best_len = 0;
744 size_t best_pos = 0;
745
746 for (size_t i = 0; i < other.size(); ++i)
747 {
748 const auto c = static_cast<unsigned char>(other[i]);
749
750 while (state != 0 and states_[static_cast<size_t>(state)].next[c] == -1)
751 {
752 state = states_[static_cast<size_t>(state)].link;
753 len = states_[static_cast<size_t>(state)].len;
754 }
755
756 if (states_[static_cast<size_t>(state)].next[c] != -1)
757 {
758 state = states_[static_cast<size_t>(state)].next[c];
759 ++len;
760 }
761 else
762 {
763 state = 0;
764 len = 0;
765 }
766
767 if (len > best_len)
768 {
769 best_len = len;
770 best_pos = i;
771 }
772 }
773
774 if (best_len == 0)
775 return "";
776
777 return std::string(other.substr(best_pos + 1 - best_len, best_len));
778 }
779
787 {
788 return states_.size();
789 }
790
798 {
799 return states_;
800 }
801};
802
817inline std::string longest_common_substring_sam(const std::string_view a, const std::string_view b)
818{
820 sam.build(a);
821 return sam.longest_common_substring(b);
822}
823} // namespace Aleph
824
825#endif // SUFFIX_STRUCTURES_H
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error()
Throws std::domain_error unconditionally.
Definition ah-errors.H:559
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:584
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:196
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Naive compressed suffix tree (didactic implementation).
Array< size_t > find_all(const std::string_view pattern) const
Return all occurrences of a pattern.
void replace_child(const size_t parent, const size_t old_child, const size_t new_child)
Naive_Suffix_Tree(const std::string_view text="")
Construct a naive suffix tree.
static constexpr size_t npos
size_t node_count() const noexcept
Return the number of nodes in the tree.
size_t create_node(const size_t start, const size_t end, const size_t parent, const size_t suffix_index)
size_t find_child_by_first_char(const size_t node, const char c) const
void build(const std::string_view text)
Build or rebuild the suffix tree for text.
size_t text_size() const noexcept
Return the text size (without terminal marker).
void insert_suffix(const size_t suffix_start)
void collect_leaf_suffixes(const size_t node, Array< size_t > &out) const
bool contains(const std::string_view pattern) const
Return true if the pattern appears in the text.
static char choose_terminal(const std::string_view s)
const Array< Node > & nodes() const noexcept
Return the internal node array for inspection/debugging.
Suffix automaton (SAM) over byte alphabet.
std::string longest_common_substring(const std::string_view other) const
Find the longest common substring between the built SAM text and other.
void build(const std::string_view text)
Build the SAM from an entire string.
bool contains(const std::string_view pattern) const
Return true if pattern is a substring of the built text.
const Array< State > & states() const noexcept
Access the internal states array for testing/inspection.
void mark_terminals()
Traverse suffix links from last and mark all states as terminal.
size_t distinct_substring_count() const
Count the number of distinct substrings in the built text.
Suffix_Automaton()
Construct an empty suffix automaton (SAM) with only the root state.
size_t state_count() const noexcept
Return the number of states in the SAM.
void clear()
Reset the automaton to its initial empty state (root only).
void extend(const char ch)
Extend the SAM with a single character.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Array< size_t > all_positions(const size_t n)
Generate an array of all positions from 0 to n.
void sort_matches(Array< size_t > &a)
Sort matches in place using introsort.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and std::convertible_to< std::invoke_result_t< const KeyFn &, size_t >, int > void counting_sort_indices(Array< size_t > &sa, Array< size_t > &tmp, const size_t n, const int min_key, const int max_key, const KeyFn &key_of)
Stable counting sort on an index array by integer keys.
Array< size_t > lcp_array_kasai(const std::string_view text, const Array< size_t > &sa)
Compute LCP array from text and suffix array using Kasai.
and
Check uniqueness with explicit hash + equality functors.
std::string longest_common_substring_sam(const std::string_view a, const std::string_view b)
Convenience function: LCS via suffix automaton.
std::vector< std::string > & split(const std::string &s, const char delim, std::vector< std::string > &elems)
Split a std::string by a single delimiter character.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
void introsort(T *a, const long l, const long r, const Compare &cmp=Compare())
Sort an array using introsort (introspective sort).
Array< size_t > suffix_array(const std::string_view text)
Build suffix array with doubling algorithm.
size_t parent
Parent node index.
size_t start
Start index in text.
size_t end
End index in text.
Array< size_t > children
Indices of children nodes.
size_t suffix_index
Index of suffix starting at leaf.
bool terminal
true if state is terminal
std::array< int, 256 > next
Transitions for each byte.
size_t first_pos
First occurrence end position.
size_t len
Max length of substring in this state.
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
Comprehensive sorting algorithms and search utilities for Aleph-w.