FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
xtrie.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4
5#include <algorithm>
6#include <array>
7#include <bit>
8#include <fstream>
9#include <iostream>
10#include <print>
11#include <ranges>
12#include <string>
13
14#ifdef FE_ABSL
15# include <absl/container/flat_hash_map.h>
16# include <absl/container/flat_hash_set.h>
17#else
18# include <unordered_map>
19# include <unordered_set>
20#endif
21
22#include "fe/arena.h"
23#include "fe/assert.h"
24#include "fe/hash.h"
25#include "fe/lct.h"
26#include "fe/vector.h"
27
28namespace fe {
29
30/// Hash-consed sets of `D*`.
31/// Small sets are sorted arrays, large ones paths in a trie; either way, equal sets are pointer-equal.
32/// This is an *IndexedTrie* as described [here](https://dl.acm.org/doi/10.1145/3808286).
33/// @p K is a *key* trait that grants access to the two `uint32_t`s XTrie needs on `D`:
34/// ```
35/// struct Key {
36/// static uint32_t gid(const D*) noexcept; ///< Unique id; orders and hashes the elements.
37/// static uint32_t tid(const D*) noexcept; ///< Trie id; `0` means "not assigned yet".
38/// static void set_tid(const D*, uint32_t) noexcept;
39/// static std::ostream& stream(std::ostream&, const D*); ///< Optional; defaults to Key::gid.
40/// };
41/// ```
42/// @p N is the maximum size of an array set; bigger sets live in the trie.
43template<class D, class K, size_t N = 16>
44class XTrie {
45private:
46 struct Hash {
47 constexpr size_t operator()(D* d) const noexcept { return fe::hash(K::gid(d)); }
48 };
49
50#ifdef FE_ABSL
51 template<class V>
52 using Map = absl::flat_hash_map<D*, V, Hash>;
53#else
54 template<class V>
55 using Map = std::unordered_map<D*, V, Hash>;
56#endif
57
58 /// Trie Node.
59 class Node : public lct::Node<Node, D*> {
60 private:
61 using LCT = lct::Node<Node, D*>;
62
63 public:
64 constexpr Node(uint32_t id) noexcept
65 : parent(nullptr)
66 , def(nullptr)
67 , size(0)
68 , min(uint32_t(-1))
69 , id(id) {}
70
71 constexpr Node(Node* parent, D* def, uint32_t id) noexcept
72 : parent(parent)
73 , def(def)
74 , size(parent->size + 1)
75 , min(parent->def ? parent->min : K::tid(def))
76 , id(id) {
77 parent->link(this);
78 }
79
80 constexpr bool lt(D* d) const noexcept { return this->is_root() || K::tid(this->def) < K::tid(d); }
81 constexpr bool eq(D* d) const noexcept { return this->def == d; }
82
83 void dot(std::ostream& os) {
84 using namespace std::string_literals;
85
86 auto node2str = [](const Node* n) {
87 return "n_"s + (n->def ? std::to_string(K::tid(n->def)) : "root"s) + "_"s + std::to_string(n->id);
88 };
89
90 std::print(os, "{} [tooltip=\"gid: {}, min: {}\"];\n", node2str(this), def ? K::gid(def) : 0, min);
91
92 for (const auto& [_, child] : children)
93 std::print(os, "{} -> {}\n", node2str(this), node2str(child.get()));
94 for (const auto& [_, child] : children)
95 child->dot(os);
96 }
97
98 ///@name Getters
99 ///@{
100 constexpr bool is_root() const noexcept { return def == nullptr; }
101
102 /// All tids on the path from the trie root to `this` live within `[Node::min, K::tid(Node::def)]`.
103 [[nodiscard]] bool contains(D* d) noexcept {
104 size_t tid = K::tid(d), lo = min, hi = K::tid(def);
105 if (tid == lo || tid == hi) return true;
106 return lo < tid && tid < hi && LCT::contains(d);
107 }
108
109 using LCT::find;
110 ///@}
111
112 Node* const parent;
113 D* const def;
114 const size_t size;
115 /// @note `uint32_t` (not `size_t`) so that it packs into the same 8 bytes as Node::id;
116 /// it only ever holds a Key::tid, which is a `uint32_t` itself.
117 const uint32_t min;
118 uint32_t const id;
119 Map<Arena::Ptr<Node>> children;
120 };
121
122 struct Data {
123 constexpr Data(size_t size) noexcept
124 : size(size) {}
125
126 size_t size;
127 D* elems[];
128
129 struct Equal {
130 constexpr bool operator()(const Data* d1, const Data* d2) const noexcept {
131 return d1->size == d2->size && std::equal(d1->begin(), d1->end(), d2->begin());
132 }
133 };
134
135 struct Hash {
136 constexpr size_t operator()(const Data* d) const noexcept {
137 auto h = hash_begin();
138 for (auto e : *d)
139 h = hash_combine(h, std::bit_cast<uintptr_t>(e));
140 return h;
141 }
142 };
143
144 /// @name Iterators
145 ///@{
146 constexpr D** begin() noexcept { return elems; }
147 constexpr D** end() noexcept { return elems + size; }
148 constexpr D* const* begin() const noexcept { return elems; }
149 constexpr D* const* end() const noexcept { return elems + size; }
150 ///@}
151
152#ifdef FE_ABSL
153 template<class H>
154 friend constexpr H AbslHashValue(H h, const Data* d) noexcept {
155 if (!d) return H::combine(std::move(h), 0);
156 return H::combine_contiguous(std::move(h), d->elems, d->size);
157 }
158#endif
159 };
160
161#ifdef FE_ABSL
162 using Pool = absl::flat_hash_set<const Data*, absl::Hash<const Data*>, typename Data::Equal>;
163#else
164 using Pool = std::unordered_set<const Data*, typename Data::Hash, typename Data::Equal>;
165#endif
166
167public:
168 class Set {
169 private:
170 enum class Tag : uintptr_t { Null, Uniq, Data, Node };
171
172 constexpr Set(const Data* data) noexcept
173 : ptr_(uintptr_t(data) | uintptr_t(Tag::Data)) {} ///< Data Set.
174 constexpr Set(Node* node) noexcept
175 : ptr_(uintptr_t(node) | uintptr_t(Tag::Node)) {} ///< Node set.
176
177 public:
178 class iterator {
179 private:
180 constexpr iterator(D* d) noexcept
181 : tag_(Tag::Uniq)
182 , ptr_(std::bit_cast<uintptr_t>(d)) {}
183 constexpr iterator(D* const* elems) noexcept
184 : tag_(Tag::Data)
185 , ptr_(std::bit_cast<uintptr_t>(elems)) {}
186 constexpr iterator(Node* node) noexcept
187 : tag_(Tag::Node)
188 , ptr_(std::bit_cast<uintptr_t>(node)) {}
189
190 public:
191 /// @name Iterator Properties
192 ///@{
193 using iterator_category = std::forward_iterator_tag;
194 using difference_type = std::ptrdiff_t;
195 using value_type = D*;
196 using pointer = D* const*;
197 using reference = D* const&;
198 ///@}
199
200 /// @name Construction
201 ///@{
202 constexpr iterator() noexcept = default;
203 ///@}
204
205 /// @name Increment
206 /// @note These operations only change the *view* of this Set; the Set itself is **not** modified.
207 ///@{
208 constexpr iterator& operator++() noexcept {
209 // clang-format off
210 switch (tag_) {
211 case Tag::Uniq: return clear();
212 case Tag::Data: return ptr_ = std::bit_cast<uintptr_t>(std::bit_cast<D* const*>(ptr_) + 1), *this;
213 case Tag::Node: {
214 auto node = std::bit_cast<Node*>(ptr_);
215 node = node->parent;
216 if (node->is_root())
217 clear();
218 else
219 ptr_ = std::bit_cast<uintptr_t>(node);
220 return *this;
221 }
222 default: unreachable();
223 }
224 // clang-format on
225 }
226
227 constexpr iterator operator++(int) noexcept {
228 auto res = *this;
229 this->operator++();
230 return res;
231 }
232 ///@}
233
234 /// @name Comparisons
235 ///@{
236 constexpr bool operator==(iterator other) const noexcept {
237 return this->tag_ == other.tag_ && this->ptr_ == other.ptr_;
238 }
239 ///@}
240
241 /// @name Dereference
242 ///@{
243 constexpr value_type operator*() const noexcept {
244 switch (tag_) {
245 case Tag::Uniq: return std::bit_cast<D*>(ptr_);
246 case Tag::Data: return *std::bit_cast<D* const*>(ptr_);
247 case Tag::Node: return std::bit_cast<Node*>(ptr_)->def;
248 default: unreachable();
249 }
250 }
251
252 constexpr value_type operator->() const noexcept { return this->operator*(); }
253 ///@}
254
255 constexpr iterator& clear() noexcept { return *this = {}; }
256
257 private:
258 Tag tag_ = Tag::Null;
259 uintptr_t ptr_ = 0;
260
261 friend class Set;
262 };
263
264 /// @name Construction
265 ///@{
266 constexpr Set(const Set&) noexcept = default;
267 constexpr Set(Set&&) noexcept = default;
268 constexpr Set() noexcept = default; ///< Null set
269 constexpr Set(D* d) noexcept
270 : ptr_(uintptr_t(d) | uintptr_t(Tag::Uniq)) {} ///< Uniq set.
271
272 constexpr Set& operator=(const Set&) noexcept = default;
273 ///@}
274
275 /// @name Getters
276 ///@{
277 constexpr size_t size() const noexcept {
278 if (isa_uniq()) return 1;
279 if (auto d = isa_data()) return d->size;
280 if (auto n = isa_node()) return n->size;
281 return 0; // empty
282 }
283
284 /// Is empty?
285 constexpr bool empty() const noexcept {
286 assert(tag() != Tag::Node || !ptr<Node>()->is_root());
287 return ptr_ == 0;
288 }
289
290 constexpr explicit operator bool() const noexcept { return !empty(); } ///< Not empty?
291 ///@}
292
293 /// @name Check Membership
294 ///@{
295
296 /// Is @f$d \in this@f$?.
297 bool contains(D* d) const noexcept {
298 if (auto u = isa_uniq()) return d == u;
299
300 if (auto data = isa_data()) {
301 for (auto e : *data)
302 if (d == e) return true;
303 return false;
304 }
305
306 if (auto n = isa_node()) return n->contains(d);
307
308 return false;
309 }
310
311 /// Is @f$this \cap other \neq \emptyset@f$?.
312 [[nodiscard]] bool has_intersection(Set other) const noexcept {
313 if (this->empty() || other.empty()) return false;
314 if (*this == other) return true;
315
316 auto u1 = this->isa_uniq();
317 auto u2 = other.isa_uniq();
318 if (u1) return other.contains(u1);
319 if (u2) return this->contains(u2);
320
321 auto d1 = this->isa_data();
322 auto d2 = other.isa_data();
323 if (d1 && d2) {
324 for (auto ai = d1->begin(), ae = d1->end(), bi = d2->begin(), be = d2->end(); ai != ae && bi != be;) {
325 if (*ai == *bi) return true;
326
327 if (K::gid(*ai) < K::gid(*bi))
328 ++ai;
329 else
330 ++bi;
331 }
332
333 return false;
334 }
335
336 auto n1 = this->isa_node();
337 auto n2 = other.isa_node();
338 if (n1 && n2) {
339 if (n1->min > K::tid(n2->def) || K::tid(n1->def) < n2->min) return false;
340 if (n1->def == n2->def) return true;
341 if (!n1->lca(n2)->is_root()) return true;
342
343 while (!n1->is_root() && !n2->is_root()) {
344 if (K::tid(n1->def) > K::tid(n2->def)) {
345 if (n1 = n1->find(n2->def); n2->def == n1->def) return true;
346 n1 = n1->parent;
347 } else {
348 if (n2 = n2->find(n1->def); n1->def == n2->def) return true;
349 n2 = n2->parent;
350 }
351 }
352
353 return false;
354 }
355
356 auto n = n1 ? n1 : n2;
357 for (auto e : *(d1 ? d1 : d2))
358 if (n->contains(e)) return true;
359
360 return false;
361 }
362 ///@}
363
364 /// @name Iterators
365 ///@{
366 constexpr iterator begin() const noexcept {
367 if (auto u = isa_uniq()) return {u};
368 if (auto d = isa_data()) return {d->begin()};
369 if (auto n = isa_node(); n && !n->is_root()) return {n};
370 return {};
371 }
372
373 constexpr iterator end() const noexcept {
374 if (auto data = isa_data()) return iterator(data->end());
375 return {};
376 }
377 ///@}
378
379 /// @name Comparisons
380 ///@{
381 constexpr bool operator==(Set other) const noexcept { return this->ptr_ == other.ptr_; }
382 ///@}
383
384 /// @name Output
385 ///@{
386 std::ostream& stream(std::ostream& os) const {
387 os << '{';
388 auto sep = "";
389 for (auto d : *this) {
390 os << sep;
391 if constexpr (requires { K::stream(os, d); })
392 K::stream(os, d);
393 else
394 os << K::gid(d);
395 sep = ", ";
396 }
397 return os << '}';
398 }
399
400 void dump() const { stream(std::cout) << std::endl; }
401 ///@}
402
403 private:
404 constexpr Tag tag() const noexcept { return Tag(ptr_ & uintptr_t(0b11)); }
405 template<class T>
406 constexpr T* ptr() const noexcept {
407 return std::bit_cast<T*>(ptr_ & ~uintptr_t(0b11));
408 }
409 // clang-format off
410 constexpr D* isa_uniq() const noexcept { return tag() == Tag::Uniq ? ptr<D >() : nullptr; }
411 constexpr Data* isa_data() const noexcept { return tag() == Tag::Data ? ptr<Data>() : nullptr; }
412 constexpr Node* isa_node() const noexcept { return tag() == Tag::Node ? ptr<Node>() : nullptr; }
413 // clang-format on
414
415 uintptr_t ptr_ = 0;
416
417 friend class XTrie;
418 friend std::ostream& operator<<(std::ostream& os, Set set) { return set.stream(os); }
419 };
420
421 static_assert(std::forward_iterator<typename Set::iterator>);
422 static_assert(std::ranges::range<Set>);
423
424 /// @name Construction
425 ///@{
426 XTrie& operator=(const XTrie&) = delete;
427
428 constexpr XTrie() noexcept
429 : root_(make_node()) {}
430 constexpr XTrie(const XTrie&) noexcept = delete;
431 constexpr XTrie(XTrie&& other) noexcept
432 : XTrie() {
433 swap(*this, other);
434 }
435 ///@}
436
437 /// @name Set Operations
438 /// @note These operations do **not** modify the input set(s); they create a **new** Set.
439 ///@{
440
441 /// Create a Set with all elements in `[begin, end)`.
442 /// @attention Reorders `[begin, end)` in place.
443 template<std::random_access_iterator I>
444 [[nodiscard]] Set create(I begin, I end) {
445 std::sort(begin, end, gid_lt);
446 auto u = std::unique(begin, end);
447 auto size = size_t(std::distance(begin, u));
448
449 if (size == 0) return {};
450 if (size == 1) return {*begin};
451
452 if (size <= N) {
453 auto [data, state] = allocate(size);
454 std::copy(begin, u, data->begin());
455 return unify(data, state);
456 }
457
458 return create_trie(begin, u);
459 }
460
461 /// Create a Set wih all elements in @p r.
462 template<std::ranges::input_range R>
463 [[nodiscard]] Set create(R&& r) {
464 auto v = fe::Vector<D*>(std::ranges::begin(r), std::ranges::end(r));
465 return create(v.begin(), v.end());
466 }
467
468 /// Create a Set wih all elements in @p list.
469 [[nodiscard]] Set create(std::initializer_list<D*> list) {
470 auto v = fe::Vector<D*>(list);
471 return create(v.begin(), v.end());
472 }
473
474 /// Yields @f$s \cup \{d\}@f$.
475 [[nodiscard]] Set insert(Set s, D* d) {
476 if (auto u = s.isa_uniq()) {
477 if (d == u) return {d};
478
479 auto [data, state] = allocate(2);
480 if (K::gid(d) < K::gid(u))
481 data->elems[0] = d, data->elems[1] = u;
482 else
483 data->elems[0] = u, data->elems[1] = d;
484 return unify(data, state);
485 }
486
487 if (auto src = s.isa_data()) {
488 auto size = src->size;
489 assert(size <= N);
490
491 for (auto e : *src)
492 if (d == e) return s; // already here
493
494 if (size == N) { // one more element is too much for a Data set: switch over to the trie
495 // Use the data arena as scratch space for the N + 1 elements; since create_trie only draws from
496 // node_arena_, it is ours to throw away again afterwards.
497 auto [scratch, state] = allocate(N + 1);
498 auto o = std::copy(src->begin(), src->end(), scratch->begin());
499 *o++ = d;
500#ifndef NDEBUG
501 auto scratch_state = data_arena_.state();
502#endif
503 auto res = create_trie(scratch->begin(), o);
504 assert(scratch_state == data_arena_.state() && "create_trie must only draw from node_arena_");
505 data_arena_.deallocate(state);
506 return res;
507 }
508
509 auto [dst, state] = allocate(size + 1);
510 auto i = std::upper_bound(src->begin(), src->end(), d, gid_lt); // where d belongs
511 auto o = std::copy(src->begin(), i, dst->begin());
512 *o++ = d;
513 std::copy(i, src->end(), o);
514 return unify(dst, state);
515 }
516
517 if (auto n = s.isa_node()) {
518 if (n->contains(d)) return n;
519 return insert(n, d);
520 }
521
522 return {d};
523 }
524
525 /// Yields @f$s_1 \cup s_2@f$.
526 [[nodiscard]] Set merge(Set s1, Set s2) {
527 if (s1.empty() || s1 == s2) return s2;
528 if (s2.empty()) return s1;
529
530 if (auto u = s1.isa_uniq()) return insert(s2, u);
531 if (auto u = s2.isa_uniq()) return insert(s1, u);
532
533 auto d1 = s1.isa_data();
534 auto d2 = s2.isa_data();
535 if (d1 && d2) {
536 // Both operands are ordered by gid and duplicate-free, so a linear merge yields the union directly -
537 // no sort, no std::unique pass.
538 // Its final size is only known afterwards, so allocate the upper bound `d1->size + d2->size` and merge
539 // straight into it; every dropped duplicate leaves one slot of excess at the tail that unify releases
540 // again.
541 auto [data, state] = allocate(d1->size + d2->size);
542 auto i1 = d1->begin(), e1 = d1->end();
543 auto i2 = d2->begin(), e2 = d2->end();
544 auto o = data->begin();
545
546 while (i1 != e1 && i2 != e2) {
547 auto g1 = K::gid(*i1);
548 auto g2 = K::gid(*i2);
549 if (g1 < g2)
550 *o++ = *i1++;
551 else if (g2 < g1)
552 *o++ = *i2++;
553 else
554 *o++ = *i1++, ++i2; // drop the duplicate
555 }
556 o = std::copy(i1, e1, o);
557 o = std::copy(i2, e2, o);
558
559 auto size = size_t(o - data->begin());
560 if (size > N) { // too big for a Data set: switch over to the trie
561#ifndef NDEBUG
562 auto scratch_state = data_arena_.state();
563#endif
564 auto res = create_trie(data->begin(), o); // only draws from node_arena_ ...
565 assert(scratch_state == data_arena_.state() && "create_trie must only draw from node_arena_");
566 data_arena_.deallocate(state); // ... so data is ours to throw away again
567 return res;
568 }
569
570 auto excess = data->size - size; // data->size is still the upper bound we allocated
571 data->size = size;
572 return unify(data, state, excess);
573 }
574
575 auto n1 = s1.isa_node();
576 auto n2 = s2.isa_node();
577 if (n1 && n2) {
578 if (n1->is_descendant_of(n2)) return n1;
579 if (n2->is_descendant_of(n1)) return n2;
580 return merge(n1, n2);
581 }
582
583 auto n = n1 ? n1 : n2;
584 for (auto d : *(d1 ? d1 : d2))
585 if (!n->contains(d)) n = insert(n, d);
586 return n;
587 }
588
589 /// Yields @f$s \setminus \{d\}@f$.
590 [[nodiscard]] Set erase(Set s, D* d) {
591 if (auto u = s.isa_uniq()) return d == u ? Set() : s;
592
593 if (auto data = s.isa_data()) {
594 auto b = data->begin(), e = data->end();
595 auto i = std::find(b, e, d);
596 if (i == e) return s; // not in here
597
598 auto size = data->size - 1;
599 if (size == 0) return {};
600 if (size == 1) return {i == b ? b[1] : b[0]};
601
602 assert(size <= N);
603 auto [new_data, state] = allocate(size);
604 std::copy(i + 1, e, std::copy(b, i, new_data->begin())); // copy over, skip i
605 return unify(new_data, state);
606 }
607
608 if (auto n = s.isa_node()) {
609 if (!n->contains(d)) return n;
610
611 auto res = erase(n, d);
612 if (res->size > N) return res;
613
614 auto v = std::array<D*, N>();
615 auto o = v.begin();
616 for (auto i = res; !i->is_root(); i = i->parent)
617 *o++ = i->def;
618 return create(v.begin(), o);
619 }
620
621 return {};
622 }
623 ///@}
624
625 /// @name DOT output
626 void dot() {
627 auto of = std::ofstream("trie.dot");
628 dot(of);
629 }
630
631 void dot(std::ostream& os) const {
632 std::print(os, "digraph {{\n");
633 std::print(os, "ordering=out;\n");
634 std::print(os, "node [shape=box,style=filled];\n");
635 root()->dot(os);
636 std::print(os, "}}\n");
637 }
638
639 friend void swap(XTrie& s1, XTrie& s2) noexcept {
640 using std::swap;
641 // clang-format off
642 swap(s1.data_arena_, s2.data_arena_);
643 swap(s1.node_arena_, s2.node_arena_);
644 swap(s1.pool_, s2.pool_);
645 swap(s1.root_, s2.root_);
646 swap(s1.tid_counter_, s2.tid_counter_);
647 swap(s1.id_counter_ , s2.id_counter_ );
648 // clang-format on
649 }
650
651private:
652 D* set_tid(D* d) noexcept {
653 assert(K::tid(d) == 0);
654 K::set_tid(d, tid_counter_++);
655 return d;
656 }
657
658 /// Data sets are ordered by Key::gid.
659 static constexpr bool gid_lt(D* d1, D* d2) noexcept { return K::gid(d1) < K::gid(d2); }
660
661 /// @name Data helpers
662 ///@{
663 std::pair<Data*, Arena::State> allocate(size_t size) {
664 auto bytes = sizeof(Data) + size * sizeof(D*);
665 auto state = data_arena_.state();
666 auto buff = data_arena_.allocate(bytes, alignof(Data));
667 auto data = new (buff) Data(size);
668 return {data, state};
669 }
670
671 /// Hash-conses @p data; rolls the arena back to @p state, if an equal Data is already pooled.
672 /// Pass the number of trailing elements allocated but not used as @p excess to release them again.
673 Set unify(Data* data, Arena::State state, size_t excess = 0) {
674 assert(data->size != 0);
675 auto [i, ins] = pool_.emplace(data);
676 if (ins) {
677 data_arena_.deallocate(excess * sizeof(D*)); // data is the arena's most recent allocation
678 return Set(data);
679 }
680
681 data_arena_.deallocate(state);
682 return Set(*i);
683 }
684 ///@}
685
686 /// Builds a trie Set from the *unique* elements in `[begin, end)`; reorders them in place.
687 /// @attention Must only ever draw from node_arena_.
688 /// Two callers use data_arena_ as scratch space and rewind it afterwards; drawing from data_arena_ in here
689 /// would pop pages that are still live - possibly including Data that pool_ still points at.
690 /// Both call sites assert this.
691 template<class I>
692 [[nodiscard]] Set create_trie(I begin, I end) {
693 // Sorting is a performance optimization, not a correctness requirement:
694 // insert() restores the canonical increasing-tid path from any insertion order, but only an element whose
695 // tid exceeds the current tip mounts in O(1) - otherwise it walks up and re-mounts the suffix in O(depth).
696 // Feeding the elements in ascending tid order therefore turns O(k * depth) into O(k) mounts.
697 // A tid of 0 goes last because set_tid hands out the next - and hence maximal - counter value.
698 std::sort(begin, end,
699 [](D* d1, D* d2) { return K::tid(d1) != 0 && (K::tid(d2) == 0 || K::tid(d1) < K::tid(d2)); });
700
701 auto res = root();
702 for (auto i = begin; i != end; ++i)
703 res = insert(res, *i);
704 return res;
705 }
706
707 // Trie helpers
708 constexpr Node* root() const noexcept { return root_.get(); }
709 Arena::Ptr<Node> make_node() { return node_arena_.mk<Node>(id_counter_++); }
710 Arena::Ptr<Node> make_node(Node* parent, D* def) { return node_arena_.mk<Node>(parent, def, id_counter_++); }
711
712 [[nodiscard]] Node* mount(Node* parent, D* d) {
713 assert(K::tid(d) != 0);
714 auto [i, ins] = parent->children.emplace(d, nullptr);
715 if (ins) i->second = make_node(parent, d);
716 return i->second.get();
717 }
718
719 [[nodiscard]] constexpr Node* insert(Node* n, D* d) noexcept {
720 if (K::tid(d) == 0) return mount(n, set_tid(d));
721 if (n->def == d) return n;
722 if (n->is_root() || K::tid(n->def) < K::tid(d)) return mount(n, d);
723 return mount(insert(n->parent, d), n->def);
724 }
725
726 [[nodiscard]] constexpr Node* merge(Node* n, Node* m) {
727 if (n == m || m->is_root()) return n;
728 if (n->is_root()) return m;
729 auto nn = K::tid(n->def) < K::tid(m->def) ? n : n->parent;
730 auto mm = K::tid(n->def) > K::tid(m->def) ? m : m->parent;
731 return mount(merge(nn, mm), K::tid(n->def) < K::tid(m->def) ? m->def : n->def);
732 }
733
734 [[nodiscard]] Node* erase(Node* n, D* d) {
735 if (K::tid(d) > K::tid(n->def)) return n;
736 if (n->def == d) return n->parent;
737 return mount(erase(n->parent, d), n->def);
738 }
739
740 Arena node_arena_;
741 Arena data_arena_;
742 Pool pool_;
743 Arena::Ptr<Node> root_;
744 uint32_t tid_counter_ = 1;
745 uint32_t id_counter_ = 0;
746};
747
748} // namespace fe
std::pair< size_t, size_t > State
Definition arena.h:88
constexpr value_type operator*() const noexcept
Definition xtrie.h:243
constexpr value_type operator->() const noexcept
Definition xtrie.h:252
std::forward_iterator_tag iterator_category
Definition xtrie.h:193
constexpr iterator() noexcept=default
constexpr iterator operator++(int) noexcept
Definition xtrie.h:227
friend class Set
Definition xtrie.h:261
std::ptrdiff_t difference_type
Definition xtrie.h:194
constexpr bool operator==(iterator other) const noexcept
Definition xtrie.h:236
constexpr iterator & operator++() noexcept
Definition xtrie.h:208
constexpr iterator & clear() noexcept
Definition xtrie.h:255
D *const & reference
Definition xtrie.h:197
constexpr iterator end() const noexcept
Definition xtrie.h:373
constexpr bool operator==(Set other) const noexcept
Definition xtrie.h:381
void dump() const
Definition xtrie.h:400
constexpr Set() noexcept=default
Null set.
constexpr iterator begin() const noexcept
Definition xtrie.h:366
constexpr Set & operator=(const Set &) noexcept=default
std::ostream & stream(std::ostream &os) const
Definition xtrie.h:386
bool has_intersection(Set other) const noexcept
Is ?.
Definition xtrie.h:312
friend std::ostream & operator<<(std::ostream &os, Set set)
Definition xtrie.h:418
constexpr size_t size() const noexcept
Definition xtrie.h:277
friend class XTrie
Definition xtrie.h:417
bool contains(D *d) const noexcept
Is ?.
Definition xtrie.h:297
constexpr bool empty() const noexcept
Is empty?
Definition xtrie.h:285
constexpr Set(const Set &) noexcept=default
constexpr Set(Set &&) noexcept=default
Set create(R &&r)
Create a Set wih all elements in r.
Definition xtrie.h:463
void dot()
Definition xtrie.h:626
void dot(std::ostream &os) const
Definition xtrie.h:631
constexpr XTrie(XTrie &&other) noexcept
Definition xtrie.h:431
Set create(std::initializer_list< D * > list)
Create a Set wih all elements in list.
Definition xtrie.h:469
constexpr XTrie() noexcept
Definition xtrie.h:428
Set insert(Set s, D *d)
Yields .
Definition xtrie.h:475
Set create(I begin, I end)
Create a Set with all elements in [begin, end).
Definition xtrie.h:444
XTrie & operator=(const XTrie &)=delete
friend void swap(XTrie &s1, XTrie &s2) noexcept
Definition xtrie.h:639
Set erase(Set s, D *d)
Yields .
Definition xtrie.h:590
Set merge(Set s1, Set s2)
Yields .
Definition xtrie.h:526
constexpr XTrie(const XTrie &) noexcept=delete
This is an intrusive Link-Cut-Tree.
Definition lct.h:21
constexpr Node() noexcept=default
constexpr Node * find(const D *&k) noexcept
Definition lct.h:40
constexpr void link(Node *child) noexcept
Registers the edge this -> child in the aux tree.
Definition lct.h:137
bool contains(const D *&k) noexcept
Definition lct.h:57
Definition algo.h:17
constexpr size_t hash(size_t h) noexcept
Mixes h with murmur3 or splitmix64 - whichever matches sizeof(size_t).
Definition hash.h:37
Vector(I, I, A=A()) -> Vector< typename std::iterator_traits< I >::value_type, Default_Inlined_Size< typename std::iterator_traits< I >::value_type >, A >
constexpr size_t hash_begin() noexcept
Seeds a hash chain with the FNV-1 offset basis.
Definition hash.h:64
constexpr size_t hash_combine(size_t seed, T v) noexcept
Mixes v into seed word-wise, reusing the FNV-1 prime as multiplier.
Definition hash.h:68
void unreachable()
Definition assert.h:31
constexpr bool operator()(const Data *d1, const Data *d2) const noexcept
Definition xtrie.h:130
constexpr size_t operator()(const Data *d) const noexcept
Definition xtrie.h:136