Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_rope.H
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
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
125#ifndef TPL_ROPE_H
126#define TPL_ROPE_H
127
128#include <algorithm>
129#include <bit>
130#include <limits>
131#include <memory>
132#include <string>
133#include <string_view>
134#include <type_traits>
135#include <utility>
136
137#include <ah-errors.H>
138#include <tpl_array.H>
139#include <tpl_small_vector.H>
140
141namespace Aleph {
142
150template <class T>
151concept RopeCharTraitsCompatible = std::is_same_v<T, char> or std::is_same_v<T, wchar_t> or
152 std::is_same_v<T, char8_t> or std::is_same_v<T, char16_t> or std::is_same_v<T, char32_t>;
153
174template <typename Char = char, size_t LeafSize = 256>
175class Rope
176{
177 static_assert(LeafSize > 0, "Rope requires a positive LeafSize");
178
179public:
181 using View = std::basic_string_view<Char>;
182
183private:
184 struct Node
185 {
186 size_t length = 0;
187 size_t depth = 0; // 0 for a leaf; 1 + max(child depths) for internal.
188 // Heap-indirected rather than embedded: `SmallVector<Char, LeafSize>`
189 // carries an inline buffer of `LeafSize * sizeof(Char)` bytes as part
190 // of its own size (see tpl_small_vector.H), so embedding it directly
191 // here would make *every* node -- including internal ones, which
192 // never hold character data -- pay that cost (e.g. 280 bytes at the
193 // default `LeafSize=256`, roughly 5x the size of everything else this
194 // struct needs). A null pointer costs 8 bytes regardless of
195 // `LeafSize`; only leaves pay for an actual buffer, and only once.
196 std::unique_ptr<const SmallVector<Char, LeafSize>> leaf_data;
197 std::shared_ptr<const Node> left; // null iff this is a leaf.
198 std::shared_ptr<const Node> right; // null iff this is a leaf.
199
201 {
202 // Both children null iff leaf: every construction path sets
203 // `left`/`right` together (never just one), so this is equivalent
204 // in practice to checking only `left` -- but checking both means a
205 // hypothetically corrupted "one child null" node is *not*
206 // misclassified as a leaf, and instead falls through to
207 // `verify_rec`'s existing `left == nullptr or right == nullptr`
208 // rejection for internal nodes.
209 return left == nullptr and right == nullptr;
210 }
211 };
212
213 using NodePtr = std::shared_ptr<const Node>;
214
215 NodePtr root_; // null means the empty rope (length 0).
216
218 {
219 auto node = std::make_shared<Node>();
220 node->length = data.size();
221 node->depth = 0;
222 node->leaf_data =
223 std::make_unique<const SmallVector<Char, LeafSize>>(std::move(data));
224 return node;
225 }
226
233 {
234 // Checked *before* adding, not after: structural sharing means a
235 // logical length near SIZE_MAX is reachable with very little real
236 // memory (e.g. ~64 self-concats of `r = r.concat(r)`, each O(1) since
237 // both sides already share the same subtree). Left unchecked, a
238 // wrapped `length` can desynchronize from the real `depth` and make
239 // `is_balanced()` wrongly decide to rebalance -- and `collect_leaves`
240 // does not deduplicate shared nodes, so rebalancing a "wide" shared
241 // structure like that visits it once per (possibly exponential)
242 // root-to-leaf path instead of once per distinct node.
243 ah_overflow_error_if(left->length > std::numeric_limits<size_t>::max() - right->length)
244 << "Rope: concatenated length would overflow size_t";
245 auto node = std::make_shared<Node>();
246 node->length = left->length + right->length;
247 node->depth = 1 + std::max(left->depth, right->depth);
248 node->left = std::move(left);
249 node->right = std::move(right);
250 return node;
251 }
252
258 {
259 return maybe_rebalance(make_internal_node(std::move(left), std::move(right)));
260 }
261
275 {
276 if (node == nullptr)
277 return nullptr;
278 if (node->is_leaf())
279 {
280 if (node->length + extra.size() > LeafSize)
281 return nullptr;
282 // No `reserve()`: the capacity check above already guarantees the
283 // merged result fits within `SmallVector`'s inline capacity
284 // (`LeafSize`), so this can never reallocate. `append_range`
285 // (rather than the copy constructor plus a per-character loop)
286 // lets a trivially copyable `Char` (the only kind `Rope` accepts,
287 // see the class's own `@tparam` note) copy both pieces via
288 // `memcpy` instead of one placement-new call per character.
290 merged.append_range(node->leaf_data->data(), node->leaf_data->size());
291 merged.append_range(extra.data(), extra.size());
292 return make_leaf(std::move(merged));
293 }
294 NodePtr new_right = try_absorb_right(node->right, extra);
295 if (new_right == nullptr)
296 return nullptr;
297 // See the matching check/comment in `make_internal_node`: `node` here
298 // can be an arbitrarily large shared subtree, so this addition needs
299 // the same overflow guard.
300 ah_overflow_error_if(node->length > std::numeric_limits<size_t>::max() - extra.size())
301 << "Rope: concatenated length would overflow size_t";
302 auto result = std::make_shared<Node>();
303 result->length = node->length + extra.size();
304 result->depth = node->depth;
305 result->left = node->left;
306 result->right = std::move(new_right);
307 return result;
308 }
309
313 [[nodiscard]] static NodePtr try_absorb_left(const NodePtr &node,
315 {
316 if (node == nullptr)
317 return nullptr;
318 if (node->is_leaf())
319 {
320 if (node->length + extra.size() > LeafSize)
321 return nullptr;
322 // No `reserve()`: see the matching note in `try_absorb_right`.
324 merged.append_range(extra.data(), extra.size());
325 merged.append_range(node->leaf_data->data(), node->leaf_data->size());
326 return make_leaf(std::move(merged));
327 }
328 NodePtr new_left = try_absorb_left(node->left, extra);
329 if (new_left == nullptr)
330 return nullptr;
331 // See the matching check/comment in `make_internal_node`.
332 ah_overflow_error_if(node->length > std::numeric_limits<size_t>::max() - extra.size())
333 << "Rope: concatenated length would overflow size_t";
334 auto result = std::make_shared<Node>();
335 result->length = node->length + extra.size();
336 result->depth = node->depth;
337 result->left = std::move(new_left);
338 result->right = node->right;
339 return result;
340 }
341
344 {
345 if (left == nullptr)
346 return right;
347 if (right == nullptr)
348 return left;
349
350 // Fast path for the common "append/prepend one small piece at a
351 // time" pattern (e.g. one character per loop iteration): if one side
352 // is a single leaf, try to absorb it into the adjacent leaf on the
353 // other side instead of growing the tree by one level. This touches
354 // only the O(depth) nodes on that spine, shares everything else, and
355 // -- because it never changes any depth -- never triggers a
356 // rebalance. Falls through to the general case if there is no spare
357 // room (or neither side is a lone leaf).
358 if (right->is_leaf())
359 if (NodePtr absorbed = try_absorb_right(left, *right->leaf_data))
360 return absorbed;
361 if (left->is_leaf())
362 if (NodePtr absorbed = try_absorb_left(right, *left->leaf_data))
363 return absorbed;
364
365 return make_internal(std::move(left), std::move(right));
366 }
367
372 [[nodiscard]] static bool is_balanced(const Node *node) noexcept
373 {
374 // No separate `depth <= 2` early return: the formula below already
375 // evaluates to `true` for any `depth` up to at least 8 (its minimum
376 // possible value, at `length == 0`), so it subsumes that case.
377 const unsigned bits = std::bit_width(node->length);
378 return node->depth <= 2 * static_cast<size_t>(bits) + 8;
379 }
380
381 static void collect_leaves(const NodePtr &node, Array<NodePtr> &out)
382 {
383 if (node->is_leaf())
384 {
385 out.append(node);
386 return;
387 }
388 collect_leaves(node->left, out);
389 collect_leaves(node->right, out);
390 }
391
404 {
405 if (node->is_leaf())
406 {
407 out.append(node);
408 return;
409 }
410 collect_leaf_pointers(node->left.get(), out);
411 collect_leaf_pointers(node->right.get(), out);
412 }
413
415 const size_t lo, const size_t hi)
416 {
417 if (hi - lo == 1)
418 return leaves[lo];
419 const size_t mid = lo + (hi - lo) / 2;
422 return make_internal_node(std::move(left), std::move(right));
423 }
424
426 {
427 if (is_balanced(node.get()))
428 return node;
430 // Estimated leaf count, computed in O(1): `length / LeafSize` is
431 // exact when every leaf is full, which is the common case (the
432 // leaf-absorption fast path keeps leaves packed). Reserving this
433 // avoids Array's geometric-growth reallocations in that common case
434 // without over-allocating -- an earlier version of this reservation
435 // used `length` itself as the bound (technically valid: no leaf is
436 // ever empty, so leaf count can't exceed it), but that is loose by
437 // up to a factor of `LeafSize` whenever leaves are reasonably full,
438 // and measurably regressed performance (over-reserving dominated the
439 // reallocations it was meant to avoid). For a pathologically
440 // fragmented tree (many near-empty leaves) this estimate undercounts
441 // and `Array` still grows geometrically from here -- no worse than
442 // not reserving at all.
443 // The `+ 1` below can only overflow `size_t` if `node->length /
444 // LeafSize` is already `SIZE_MAX` -- reachable only with the
445 // degenerate `LeafSize == 1` and a `length` at or near `SIZE_MAX`
446 // (see `make_internal_node`'s overflow guard for how a rope reaches
447 // such a length cheaply via structural sharing). Guarded rather than
448 // left to wrap: a wrapped-to-0 reserve would silently defeat the
449 // estimate instead of just being loose, though either way `Array`
450 // still grows correctly from there (see the comment above).
451 const size_t estimated_leaf_count = node->length / LeafSize;
452 leaves.reserve(estimated_leaf_count == std::numeric_limits<size_t>::max()
454 collect_leaves(node, leaves);
455 return build_balanced_from_leaves(leaves, 0, leaves.size());
456 }
457
463 {
464 if (v.size() <= LeafSize)
465 {
466 // No `reserve()` here: `v.size() <= LeafSize` is exactly
467 // `SmallVector<Char, LeafSize>`'s inline capacity, so it can never
468 // reallocate regardless. `View` (`basic_string_view<Char>`) is
469 // contiguous, so `append_range` can copy it in one `memcpy`
470 // instead of one placement-new call per character.
472 data.append_range(v.data(), v.size());
473 return make_leaf(std::move(data));
474 }
475 const size_t mid = v.size() / 2;
476 NodePtr left = build_from_view(v.substr(0, mid));
477 NodePtr right = build_from_view(v.substr(mid));
478 return make_internal_node(std::move(left), std::move(right));
479 }
480
481 [[nodiscard]] static const Char &char_at(const Node *node, size_t pos) noexcept
482 {
483 while (not node->is_leaf())
484 if (const size_t left_len = node->left->length; pos < left_len)
485 node = node->left.get();
486 else
487 {
488 pos -= left_len;
489 node = node->right.get();
490 }
491 // Unchecked access: `pos` is guaranteed in range by the descent above
492 // (and, transitively, by `at()`'s own bounds check before calling
493 // here). Using the checked `operator[]` would make this `noexcept`
494 // function capable of calling `std::terminate()` on `std::out_of_range`
495 // instead of safely returning -- and would repeat a bounds check that
496 // can never fail.
497 return (*node->leaf_data)(pos);
498 }
499
505 [[nodiscard]] static NodePtr slice(const NodePtr &node, size_t pos, size_t len)
506 {
507 if (len == 0)
508 return nullptr;
509
510 // Whole-node request: share `node` itself, regardless of whether it is
511 // a leaf or an internal node. Without this check here, an internal
512 // node's own "whole subtree" case fell through to the straddle branch
513 // below, which slices both children (trivially, via the same
514 // fast path) and then *rejoins* them with `concat_nodes` -- producing
515 // a freshly-built node with identical content instead of sharing
516 // `node`. That defeated the O(1) sharing `substr(0, size())` is
517 // documented to provide for internal-rooted ropes.
518 if (pos == 0 and len == node->length)
519 return node;
520
521 if (node->is_leaf())
522 {
523 // No `reserve()`: `len < node->length <= LeafSize` here (the
524 // `len == node->length` case already returned above), so this
525 // always stays within `SmallVector`'s inline capacity.
526 // `append_range` copies the `[pos, pos+len)` subrange via one
527 // `memcpy` instead of one placement-new call per character.
529 data.append_range(node->leaf_data->data() + pos, len);
530 return make_leaf(std::move(data));
531 }
532
533 const size_t left_len = node->left->length;
534
535 if (pos + len <= left_len)
536 {
537 if (pos == 0 and len == left_len)
538 return node->left;
539 return slice(node->left, pos, len);
540 }
541
542 if (pos >= left_len)
543 {
544 const size_t right_pos = pos - left_len;
545 if (right_pos == 0 and len == node->right->length)
546 return node->right;
547 return slice(node->right, right_pos, len);
548 }
549
550 // Straddles both children: take the tail of `left` and the head of
551 // `right`, then rejoin (may trigger a rebalance).
552 const size_t left_part_len = left_len - pos;
553 NodePtr left_part = slice(node->left, pos, left_part_len);
554 NodePtr right_part = slice(node->right, 0, len - left_part_len);
555 return concat_nodes(std::move(left_part), std::move(right_part));
556 }
557
558 static void flatten_into(const Node *node, Array<Char> &out)
559 {
560 if (node == nullptr)
561 return;
562 if (node->is_leaf())
563 {
564 for (const Char &c : *node->leaf_data)
565 out.append(c);
566 return;
567 }
568 flatten_into(node->left.get(), out);
569 flatten_into(node->right.get(), out);
570 }
571
576 static void string_append_into(const Node *node, std::basic_string<Char> &out)
578 {
579 if (node == nullptr)
580 return;
581 if (node->is_leaf())
582 {
583 out.append(node->leaf_data->data(), node->leaf_data->size());
584 return;
585 }
586 string_append_into(node->left.get(), out);
587 string_append_into(node->right.get(), out);
588 }
589
598 [[nodiscard]] static bool verify_rec(const Node *node) noexcept
599 {
600 if (node == nullptr)
601 return true;
602 if (node->is_leaf())
603 return node->leaf_data != nullptr and node->leaf_data->size() == node->length and
604 node->length >= 1 and node->length <= LeafSize and node->depth == 0;
605 if (node->leaf_data != nullptr)
606 return false;
607 if (node->left == nullptr or node->right == nullptr)
608 return false;
609 if (node->length != node->left->length + node->right->length)
610 return false;
611 if (node->depth != 1 + std::max(node->left->depth, node->right->depth))
612 return false;
613 return verify_rec(node->left.get()) and verify_rec(node->right.get());
614 }
615
616 explicit Rope(NodePtr root) noexcept : root_(std::move(root)) {}
617
618public:
623
628 explicit Rope(const View v) : root_(v.empty() ? nullptr : build_from_view(v)) {}
629
635 Rope(const Rope &other) = default;
641 Rope &operator = (const Rope &other) = default;
646 Rope(Rope &&other) noexcept = default;
652 Rope &operator = (Rope &&other) noexcept = default;
655 ~Rope() = default;
656
662 {
663 return root_ == nullptr ? 0 : root_->length;
664 }
665
671 {
672 return root_ == nullptr;
673 }
674
683 [[nodiscard]] Char at(const size_t pos) const
684 {
685 ah_out_of_range_error_if(pos >= size()) << "Rope::at(): index out of range";
686 return char_at(root_.get(), pos);
687 }
688
708 [[nodiscard]] Rope concat(const Rope &other) const
709 {
710 return Rope(concat_nodes(root_, other.root_));
711 }
712
730 [[nodiscard]] Rope substr(const size_t pos, const size_t len) const
731 {
732 ah_out_of_range_error_if(pos > size() or len > size() - pos)
733 << "Rope::substr(): range out of bounds";
734 return Rope(slice(root_, pos, len));
735 }
736
753 [[nodiscard]] Rope insert(const size_t pos, const Rope &other) const
754 {
755 ah_out_of_range_error_if(pos > size()) << "Rope::insert(): index out of range";
756 if (other.is_empty())
757 return *this;
758 return substr(0, pos).concat(other).concat(substr(pos, size() - pos));
759 }
760
775 [[nodiscard]] Rope erase(const size_t pos, const size_t len) const
776 {
777 ah_out_of_range_error_if(pos > size() or len > size() - pos)
778 << "Rope::erase(): range out of bounds";
779 if (len == 0)
780 return *this;
781 return substr(0, pos).concat(substr(pos + len, size() - pos - len));
782 }
783
791 {
792 Array<Char> result;
793 result.reserve(size());
794 flatten_into(root_.get(), result);
795 return result;
796 }
797
813 [[nodiscard]] std::basic_string<Char> to_string() const
815 {
816 std::basic_string<Char> result;
817 ah_length_error_if(size() > result.max_size())
818 << "Rope::to_string(): result exceeds std::basic_string<Char>::max_size()";
819 result.reserve(size());
820 string_append_into(root_.get(), result);
821 return result;
822 }
823
840 [[nodiscard]] bool operator == (const Rope &other) const
841 {
842 if (size() != other.size())
843 return false;
844 if (root_ == other.root_)
845 return true; // same shared tree (or both empty): trivially equal.
846
848 // Same reserve rationale (and same `+ 1` overflow guard) as
849 // maybe_rebalance(): `length / LeafSize` is exact when leaves are
850 // packed (the common case), avoiding Array's geometric-growth
851 // reallocations without over-allocating.
852 const size_t estimated_leaf_count = size() / LeafSize;
853 const size_t leaf_reserve = estimated_leaf_count == std::numeric_limits<size_t>::max()
856 theirs.reserve(leaf_reserve);
858 collect_leaf_pointers(other.root_.get(), theirs);
859
860 size_t i = 0, j = 0, i_off = 0, j_off = 0;
861 while (i < mine.size())
862 {
863 if (i_off == 0 and j_off == 0 and mine[i] == theirs[j])
864 {
865 // Same shared leaf node on both sides: identical by
866 // construction (Rope never mutates a leaf in place), skip
867 // straight to the next pair without comparing characters.
868 ++i;
869 ++j;
870 continue;
871 }
872 const Node &a = *mine[i];
873 const Node &b = *theirs[j];
874 const size_t n = std::min(a.length - i_off, b.length - j_off);
875 // std::equal over a raw pointer range (rather than a hand-rolled
876 // per-index loop) gives the compiler a standard, recognizable
877 // pattern -- for `Char` types where equality is the built-in
878 // one, this is commonly folded into a single memcmp-style bulk
879 // comparison instead of one function-call-per-character.
880 const Char *pa = a.leaf_data->data() + i_off;
881 const Char *pb = b.leaf_data->data() + j_off;
882 if (not std::equal(pa, pa + n, pb))
883 return false;
884 i_off += n;
885 j_off += n;
886 if (i_off == a.length)
887 {
888 ++i;
889 i_off = 0;
890 }
891 if (j_off == b.length)
892 {
893 ++j;
894 j_off = 0;
895 }
896 }
897 return true;
898 }
899
928 {
929 return verify_rec(root_.get());
930 }
931};
932
933} // namespace Aleph
934
935#endif // TPL_ROPE_H
Exception handling system with formatted messages for Aleph-w.
#define ah_length_error_if(C)
Throws std::length_error if condition holds.
Definition ah-errors.H:703
#define ah_out_of_range_error_if(C)
Throws std::out_of_range if condition holds.
Definition ah-errors.H:584
#define ah_overflow_error_if(C)
Throws std::overflow_error if condition holds.
Definition ah-errors.H:468
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
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Immutable, structurally-shared rope over a sequence of Char.
Definition tpl_rope.H:176
Rope erase(const size_t pos, const size_t len) const
Return a new rope with [pos, pos+len) removed.
Definition tpl_rope.H:775
static NodePtr slice(const NodePtr &node, size_t pos, size_t len)
Extract [pos, pos+len) from node (whose own length is assumed to be >= pos+len) as a new,...
Definition tpl_rope.H:505
bool is_empty() const noexcept
Check whether this rope holds no characters.
Definition tpl_rope.H:670
Rope & operator=(const Rope &other)=default
Copy assignment operator: O(1), shares the entire tree.
~Rope()=default
Destructor: releases this rope's reference to its tree; nodes are only actually freed once no rope sh...
Rope(NodePtr root) noexcept
Definition tpl_rope.H:616
Rope insert(const size_t pos, const Rope &other) const
Return a new rope with other inserted at pos.
Definition tpl_rope.H:753
static NodePtr build_from_view(const View v)
Build a balanced tree of leaves directly from a flat view, splitting at the midpoint recursively.
Definition tpl_rope.H:462
static void collect_leaf_pointers(const Node *node, Array< const Node * > &out)
Read-only counterpart of collect_leaves: collects raw, non-owning const Node * instead of NodePtr (sh...
Definition tpl_rope.H:403
static NodePtr make_internal_node(NodePtr left, NodePtr right)
Raw construction of an internal node from two already-built, non-null subtrees: no rebalancing,...
Definition tpl_rope.H:232
static NodePtr maybe_rebalance(NodePtr node)
Definition tpl_rope.H:425
Rope() noexcept=default
Construct the empty rope.
std::basic_string_view< Char > View
View type accepted by the constructor and compared against.
Definition tpl_rope.H:181
static void string_append_into(const Node *node, std::basic_string< Char > &out)
Like flatten_into, but appends directly into a std::basic_string (one bulk append per leaf) instead o...
Definition tpl_rope.H:576
Array< Char > flatten() const
Return every character of this rope as an independent Array.
Definition tpl_rope.H:790
static bool is_balanced(const Node *node) noexcept
Generous upper bound on the depth a subtree of the given length should have if reasonably balanced – ...
Definition tpl_rope.H:372
Rope concat(const Rope &other) const
Return a new rope that is *this followed by other.
Definition tpl_rope.H:708
static NodePtr try_absorb_right(const NodePtr &node, const SmallVector< Char, LeafSize > &extra)
Try to merge extra into the rightmost leaf of node, sharing every other node unchanged.
Definition tpl_rope.H:273
bool operator==(const Rope &other) const
Equality: true iff both ropes have the same length and the same characters in the same order (structu...
Definition tpl_rope.H:840
std::basic_string< Char > to_string() const
Return every character of this rope as a std::basic_string.
Definition tpl_rope.H:813
std::shared_ptr< const Node > NodePtr
Definition tpl_rope.H:213
static NodePtr concat_nodes(NodePtr left, NodePtr right)
Concatenate two (possibly null/empty) subtrees.
Definition tpl_rope.H:343
static NodePtr make_internal(NodePtr left, NodePtr right)
Wrap two non-null subtrees in a new internal node, then rebalance if the result is deeper than is_bal...
Definition tpl_rope.H:257
static void flatten_into(const Node *node, Array< Char > &out)
Definition tpl_rope.H:558
size_t size() const noexcept
Return the number of characters in this rope.
Definition tpl_rope.H:661
NodePtr root_
Definition tpl_rope.H:215
Rope(const Rope &other)=default
Copy constructor: O(1), shares the entire tree (immutable, so sharing is always safe).
static const Char & char_at(const Node *node, size_t pos) noexcept
Definition tpl_rope.H:481
bool verify() const noexcept
Check this rope's internal structural invariants.
Definition tpl_rope.H:927
Rope(Rope &&other) noexcept=default
Move constructor.
static void collect_leaves(const NodePtr &node, Array< NodePtr > &out)
Definition tpl_rope.H:381
Rope substr(const size_t pos, const size_t len) const
Return a new rope holding [pos, pos+len) of *this.
Definition tpl_rope.H:730
static NodePtr make_leaf(SmallVector< Char, LeafSize > data)
Definition tpl_rope.H:217
static NodePtr try_absorb_left(const NodePtr &node, const SmallVector< Char, LeafSize > &extra)
Mirror of try_absorb_right: merge extra into the leftmost leaf of node, for the "prepend one small pi...
Definition tpl_rope.H:313
static bool verify_rec(const Node *node) noexcept
Recursive structural-invariant check backing the public verify().
Definition tpl_rope.H:598
static NodePtr build_balanced_from_leaves(const Array< NodePtr > &leaves, const size_t lo, const size_t hi)
Definition tpl_rope.H:414
Char at(const size_t pos) const
Return the character at pos.
Definition tpl_rope.H:683
Contiguous dynamic array with N elements of inline storage.
size_t size() const noexcept
Return the number of stored elements. O(1).
void append_range(const T *first, const size_t count)
Append count copies from [first, first + count), in order.
size_t length() const noexcept
Count the number of elements of a container.
Definition ah-dry.H:1725
True exactly for the character types std::char_traits is specialized for by the standard (char,...
Definition tpl_rope.H:151
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4071
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
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
std::unique_ptr< const SmallVector< Char, LeafSize > > leaf_data
Definition tpl_rope.H:196
std::shared_ptr< const Node > left
Definition tpl_rope.H:197
std::shared_ptr< const Node > right
Definition tpl_rope.H:198
bool is_leaf() const noexcept
Definition tpl_rope.H:200
Dynamic array container with automatic resizing.
Dynamic array with inline storage (Aleph::SmallVector).