FE 0.16.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
patricia.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <cstddef>
5#include <cstdint>
6
7#include <algorithm>
8#include <array>
9#include <bit>
10#include <functional>
11#include <initializer_list>
12#include <iostream>
13#include <iterator>
14#include <memory>
15#include <ostream>
16#include <print>
17#include <ranges>
18#include <tuple>
19#include <utility>
20
21#include <ankerl/unordered_dense.h>
22
23#include "fe/arena.h"
24#include "fe/assert.h"
25#include "fe/hash.h"
26#include "fe/span.h"
27#include "fe/vector.h"
28
29namespace fe {
30
31/// Hash-consed, immutable sets of `D*`, ordered by an unsigned id the elements carry themselves.
32/// A [Patricia tree](https://dl.acm.org/doi/10.1145/321479.321481) - a prefix trie over the big-endian bits of
33/// the id, as in Okasaki and Gill's [Fast Mergeable Integer Maps](https://ku-fpg.github.io/papers/Okasaki-98-IntMap/)
34/// - in the four flavours below, picked by size alone; equal sets are therefore *pointer-equal*.
35///
36/// | Flavour | Holds |
37/// |---------|-------|
38/// | empty | nothing; a Set that converts to `false` |
39/// | `Uniq` | exactly one `D*` - inline in the Set itself, so a singleton costs no node at all |
40/// | `Arr` | 2 to @p N elements, sorted by id |
41/// | `Br` | more than @p N elements: a prefix, its branching bit, and two non-empty children |
42///
43/// The flavour is picked for *every* subtree, so a branch's children are again single elements, arrays, or
44/// branches - which collapses the bottom @f$\log_2 N@f$ levels of the trie into one array each.
45/// Patricia shape is canonical (the branching bit of an id set is just the highest bit its minimum and maximum
46/// disagree on), so this keeps one element set mapped to exactly one representation.
47///
48/// Ids are ordered as **unsigned**, i.e. iteration yields `0` first and `K(-1)` last.
49/// @p KT is a *key* trait:
50/// ```
51/// struct Key {
52/// static K key(const D*) noexcept; ///< Unique id; orders the elements.
53/// static std::ostream& stream(std::ostream&, const D*); ///< Optional; defaults to Key::key.
54/// };
55/// ```
56/// @attention Key::key *is* the identity: two elements sharing an id are the same element to a Set, and which of
57/// them survives an operation is unspecified.
58/// Every `D` must be at least 4-byte aligned, since a Set tags the two low bits of its word.
59/// @note All operations yield a **new** Set; none of them modify their input.
60/// Nothing is ever freed - the Arena%s release everything at once when the Patricia dies.
61template<class D, class KT, class K = uint32_t, size_t N = 8>
62class Patricia {
63 static_assert(std::unsigned_integral<K>, "Patricia ids are ordered as unsigned");
64 static_assert(N >= 2, "an array node holds 2 to N elements");
65
66private:
67 /// @name Okasaki Bit Twiddling
68 ///@{
69 /// Bits strictly above the branching bit @p m; `0` if @p m is the top bit.
70 static constexpr K above(K m) noexcept { return K(K(0) - K(m << 1)); }
71 /// Is @p k left of the branching bit @p m?
72 static constexpr bool zero_bit(K k, K m) noexcept { return (k & m) == 0; }
73 /// @p k with the branching bit @p m and everything below it cleared; @p k itself for the leaf mask `0`.
74 static constexpr K mask_of(K k, K m) noexcept { return m == 0 ? k : K(k & above(m)); }
75 /// The highest bit @p k1 and @p k2 disagree on; `0` iff they are equal.
76 static constexpr K branching_bit(K k1, K k2) noexcept { return std::bit_floor(K(k1 ^ k2)); }
77 /// Does @p k live under the prefix @p p with branching bit @p m?
78 static constexpr bool match_prefix(K k, K p, K m) noexcept { return mask_of(k, m) == p; }
79 ///@}
80
81 /// An element and its id.
82 /// The id is redundant - but reading it through `KT::key` would chase the pointer on every comparison.
83 struct Entry {
84 K key;
85 D* d;
86 };
87
88 /// 2 to @p N entries, sorted by id.
89 struct Arr {
90 constexpr Arr(size_t hash, uint32_t size) noexcept
91 : hash(hash)
92 , size(size) {}
93
94 size_t hash; ///< Cached: the pool rehashes, so recomputing this over all Arr::entries hurts.
95 uint32_t size;
96 Entry entries[];
97 };
98
99 /// More than @p N elements.
100 struct Br {
101 constexpr Br(K prefix, K mask, size_t size, uintptr_t l, uintptr_t r) noexcept
102 : prefix(prefix)
103 , mask(mask)
104 , size(size)
105 , l(l)
106 , r(r) {}
107
108 K prefix; ///< What all ids below agree on; Br::mask and everything under it cleared.
109 K mask; ///< The branching bit.
110 size_t size; ///< `> N`.
111 uintptr_t l; ///< Tagged Set word of the child with Br::mask clear; never empty.
112 uintptr_t r; ///< Tagged Set word of the child with Br::mask set; never empty.
113 };
114
115 static_assert(alignof(Arr) >= 4 && alignof(Br) >= 4);
116
117 /// How the id spans of two branches relate - the case analysis every binary operation below walks.
118 enum class Rel {
119 Same, ///< The same span: their children pair up.
120 L1, ///< The second one lives under the first one's left child.
121 R1, ///< ... under its right child.
122 L2, ///< The first one lives under the second one's left child.
123 R2, ///< ... under its right child.
124 None, ///< Disjoint spans.
125 };
126
127 static Rel rel(const Br* n1, const Br* n2) noexcept {
128 if (n1->mask == n2->mask && n1->prefix == n2->prefix) return Rel::Same;
129
130 if (n1->mask > n2->mask && match_prefix(n2->prefix, n1->prefix, n1->mask))
131 return zero_bit(n2->prefix, n1->mask) ? Rel::L1 : Rel::R1;
132
133 if (n2->mask > n1->mask && match_prefix(n1->prefix, n2->prefix, n2->mask))
134 return zero_bit(n1->prefix, n2->mask) ? Rel::L2 : Rel::R2;
135
136 return Rel::None;
137 }
138
139public:
140 /// An immutable set - really just a tagged pointer, so copy it around freely.
141 class Set {
142 private:
143 /// An untagged word *is* the `D*` it holds, so a `Uniq` needs no node - and `0` is the empty Set.
144 enum class Tag : uintptr_t { Uniq = 0, Arr = 1, Br = 2 };
145
146 static constexpr uintptr_t Tag_Mask = 0b11;
147
148 constexpr explicit Set(uintptr_t word) noexcept
149 : word_(word) {}
150 constexpr Set(const Arr* n) noexcept
151 : word_(std::bit_cast<uintptr_t>(n) | uintptr_t(Tag::Arr)) {}
152 constexpr Set(const Br* n) noexcept
153 : word_(std::bit_cast<uintptr_t>(n) | uintptr_t(Tag::Br)) {}
154
155 constexpr Tag tag() const noexcept { return Tag(word_ & Tag_Mask); }
156 template<class T>
157 constexpr const T* ptr() const noexcept {
158 return std::bit_cast<const T*>(word_ & ~Tag_Mask);
159 }
160 // clang-format off
161 /// The one element of a `Uniq` - `nullptr` for every other flavour, the empty Set included.
162 constexpr D* isa_uniq() const noexcept { return tag() == Tag::Uniq ? std::bit_cast<D*>(word_) : nullptr; }
163 constexpr const Arr* isa_arr() const noexcept { return tag() == Tag::Arr ? ptr<Arr>() : nullptr; }
164 constexpr const Br* isa_br () const noexcept { return tag() == Tag::Br ? ptr<Br >() : nullptr; }
165 constexpr Set left () const noexcept { return Set(ptr<Br>()->l); }
166 constexpr Set right() const noexcept { return Set(ptr<Br>()->r); }
167 // clang-format on
168
169 /// The entries of an `Arr`; empty for every other flavour - a `Uniq` has no node to point at.
170 constexpr View<Entry> entries() const noexcept {
171 if (auto n = isa_arr()) return View<Entry>(n->entries, size_t(n->size));
172 return {};
173 }
174
175 /// What all ids agree on - the id itself for a `Uniq`.
176 K prefix() const noexcept {
177 if (auto n = isa_br()) return n->prefix;
178 if (auto d = isa_uniq()) return KT::key(d);
179 auto es = entries();
180 return mask_of(es.front().key, branching_bit(es.front().key, es.back().key));
181 }
182
183 public:
184 /// Yields the `D*` in ascending Key::key order.
185 /// @note The trie is at most `8 * sizeof(K)` deep, so the path stack sits inside the iterator.
186 class iterator {
187 public:
188 using iterator_category = std::forward_iterator_tag;
189 using difference_type = std::ptrdiff_t;
190 using value_type = D*;
191 using pointer = D*;
192 using reference = D*;
193
194 iterator() noexcept = default;
195
196 reference operator*() const noexcept { return uniq_ ? uniq_ : i_->d; }
197 pointer operator->() const noexcept { return this->operator*(); }
198
199 iterator& operator++() noexcept {
200 if (uniq_) return uniq_ = nullptr, advance();
201 return ++i_ == e_ ? advance() : *this;
202 }
203
204 iterator operator++(int) noexcept {
205 auto res = *this;
206 this->operator++();
207 return res;
208 }
209
210 bool operator==(const iterator& other) const noexcept { return i_ == other.i_ && uniq_ == other.uniq_; }
211
212 private:
213 explicit iterator(uintptr_t w) noexcept {
214 if (w != 0) descend(w);
215 }
216
217 /// Walks to the leftmost block below @p w, remembering the branches on the way.
218 void descend(uintptr_t w) noexcept {
219 for (; Tag(w & Tag_Mask) == Tag::Br; w = path_[depth_++]->l) {
220 // A branch's mask strictly decreases downwards and the deepest one still spans > N ids.
221 assert(depth_ < path_.size());
222 path_[depth_] = std::bit_cast<const Br*>(w & ~Tag_Mask);
223 left_ |= uint64_t(1) << depth_;
224 }
225
226 if (Tag(w & Tag_Mask) == Tag::Uniq) {
227 uniq_ = std::bit_cast<D*>(w);
228 i_ = e_ = nullptr;
229 } else {
230 auto n = std::bit_cast<const Arr*>(w & ~Tag_Mask);
231 i_ = n->entries;
232 e_ = n->entries + n->size;
233 }
234 }
235
236 /// Climbs to the nearest branch we are still in the left child of and takes its right one.
237 iterator& advance() noexcept {
238 for (; depth_ != 0; --depth_) {
239 auto d = depth_ - 1;
240 if (left_ & (uint64_t(1) << d)) {
241 left_ &= ~(uint64_t(1) << d);
242 descend(path_[d]->r);
243 return *this;
244 }
245 }
246
247 uniq_ = nullptr;
248 i_ = e_ = nullptr;
249 return *this;
250 }
251
252 D* uniq_ = nullptr; ///< The element of a `Uniq`, which has no node to point into.
253 const Entry* i_ = nullptr;
254 const Entry* e_ = nullptr;
255 std::array<const Br*, 8 * sizeof(K)> path_{};
256 uint64_t left_ = 0; ///< One bit per level: are we in `path_[level]`'s left child?
257 size_t depth_ = 0;
258
259 friend class Set;
260 };
261
262 /// @name Construction
263 ///@{
264 constexpr Set() noexcept = default; ///< The empty set.
265
266 /// The singleton @f$\{d\}@f$ - stored inline, so this allocates nothing.
267 constexpr explicit Set(D* d) noexcept
268 : word_(std::bit_cast<uintptr_t>(d)) {
269 assert((word_ & Tag_Mask) == 0 && "a D must be at least 4-byte aligned");
270 }
271 ///@}
272
273 /// @name Getters
274 ///@{
275 size_t size() const noexcept {
276 if (auto n = isa_arr()) return n->size;
277 if (auto n = isa_br()) return n->size;
278 return empty() ? 0 : 1;
279 }
280
281 constexpr bool empty() const noexcept { return word_ == 0; }
282 constexpr explicit operator bool() const noexcept { return !empty(); } ///< Not empty?
283
284 D* min() const noexcept { return edge(false); } ///< Smallest id - or `nullptr`.
285 D* max() const noexcept { return edge(true); } ///< Largest id - or `nullptr`.
286 ///@}
287
288 /// @name Check Membership
289 ///@{
290 bool contains(D* d) const noexcept { return lookup(KT::key(d)) != nullptr; }
291
292 /// Is @f$this \cap other \neq \emptyset@f$?
293 [[nodiscard]] bool has_intersection(Set other) const noexcept {
294 if (this->empty() || other.empty()) return false;
295 if (*this == other) return true;
296
297 auto n1 = this->isa_br();
298 auto n2 = other.isa_br();
299
300 if (!n1) return this->any_in(other);
301 if (!n2) return other.any_in(*this);
302
303 switch (rel(n1, n2)) {
304 case Rel::Same:
305 return this->left().has_intersection(other.left()) || this->right().has_intersection(other.right());
306 case Rel::L1: return this->left().has_intersection(other);
307 case Rel::R1: return this->right().has_intersection(other);
308 case Rel::L2: return this->has_intersection(other.left());
309 case Rel::R2: return this->has_intersection(other.right());
310 case Rel::None: return false;
311 }
312 std::unreachable();
313 }
314
315 /// Is @f$this \subseteq other@f$?
316 [[nodiscard]] bool subset_of(Set other) const noexcept {
317 if (*this == other || this->empty()) return true;
318 if (this->size() > other.size()) return false;
319
320 auto n1 = this->isa_br();
321 auto n2 = other.isa_br();
322
323 if (!n1) return this->all_in(other);
324 if (!n2) return false; // a Br holds more than N elements, so the size check above already caught this
325
326 switch (rel(n1, n2)) {
327 case Rel::Same: return this->left().subset_of(other.left()) && this->right().subset_of(other.right());
328 case Rel::L2: return this->subset_of(other.left());
329 case Rel::R2: return this->subset_of(other.right());
330 default: return false; // `this` spans ids `other` does not even branch on
331 }
332 }
333 ///@}
334
335 /// @name Iterators
336 /// Ascending by id, compared as **unsigned**.
337 ///@{
338 iterator begin() const noexcept { return iterator(word_); }
339 iterator end() const noexcept { return {}; }
340
341 /// Like iterating, but without the iterator's path stack.
342 void for_each(auto&& f) const {
343 if (isa_br()) {
344 left().for_each(f);
345 right().for_each(f);
346 } else if (auto d = isa_uniq()) {
347 std::invoke(f, d);
348 } else {
349 for (const auto& e : entries())
350 std::invoke(f, e.d);
351 }
352 }
353 ///@}
354
355 /// @name Comparisons
356 /// Everything is hash-consed and a singleton is always `Uniq`, so this compares contents in `O(1)`.
357 ///@{
358 constexpr bool operator==(Set other) const noexcept { return this->word_ == other.word_; }
359 ///@}
360
361 /// @name Output
362 ///@{
363 std::ostream& stream(std::ostream& os) const {
364 os << '{';
365 auto sep = "";
366 for (auto d : *this) {
367 os << sep;
368 if constexpr (requires { KT::stream(os, d); })
369 KT::stream(os, d);
370 else
371 os << +KT::key(d);
372 sep = ", ";
373 }
374 return os << '}';
375 }
376
377 void dump() const { stream(std::cout) << std::endl; }
378
379 void dot(std::ostream& os) const {
380 std::print(os, "digraph {{\nordering=out;\nnode [shape=box,style=filled];\n");
381 dot(os, *this);
382 std::print(os, "}}\n");
383 }
384 ///@}
385
386 private:
387 /// The element with @p key - or `nullptr`.
388 /// A block holds at most @p N elements, so a scan beats a binary search; the *insertion* point still wants
389 /// `std::lower_bound`, since a miss would scan the whole block.
390 D* lookup(K key) const noexcept {
391 for (auto s = *this;;) {
392 if (auto n = s.isa_br()) {
393 if (!match_prefix(key, n->prefix, n->mask)) return nullptr;
394 s = zero_bit(key, n->mask) ? s.left() : s.right();
395 } else if (auto d = s.isa_uniq()) {
396 return KT::key(d) == key ? d : nullptr;
397 } else {
398 for (const auto& e : s.entries())
399 if (e.key == key) return e.d;
400 return nullptr;
401 }
402 }
403 }
404
405 D* edge(bool last) const noexcept {
406 auto s = *this;
407 while (auto n = s.isa_br())
408 s = last ? s.right() : s.left();
409 if (auto d = s.isa_uniq()) return d;
410 auto es = s.entries();
411 return es.empty() ? nullptr : (last ? es.back().d : es.front().d);
412 }
413
414 /// @name Block Predicates
415 /// `this` is not a `Br` and hence holds at most @p N elements.
416 ///@{
417 bool any_in(Set other) const noexcept {
418 if (auto d = isa_uniq()) return other.contains(d);
419 return std::ranges::any_of(entries(), [other](const Entry& e) { return other.lookup(e.key); });
420 }
421
422 bool all_in(Set other) const noexcept {
423 if (auto d = isa_uniq()) return other.contains(d);
424 return std::ranges::all_of(entries(), [other](const Entry& e) { return other.lookup(e.key); });
425 }
426 ///@}
427
428 /// Appends the entries this very node stores - none for a `Br` - to @p o.
429 template<class O>
430 O copy(O o) const noexcept {
431 if (auto d = isa_uniq()) return *o++ = Entry{KT::key(d), d}, o;
432 return std::ranges::copy(entries(), o).out;
433 }
434
435 static void dot(std::ostream& os, Set s) {
436 if (auto n = s.isa_br()) {
437 std::print(os, "n{} [label=\"{:#x}/{:#x}\"];\n", s.word_, uint64_t(n->prefix), uint64_t(n->mask));
438 for (auto child : {s.left(), s.right()}) {
439 std::print(os, "n{} -> n{};\n", s.word_, child.word_);
440 dot(os, child);
441 }
442 } else {
443 std::print(os, "n{} [label=\"", s.word_);
444 s.stream(os);
445 std::print(os, "\"];\n");
446 }
447 }
448
449 uintptr_t word_ = 0;
450
451 friend class Patricia;
452 friend std::ostream& operator<<(std::ostream& os, Set s) { return s.stream(os); }
453 };
454
455 static_assert(std::forward_iterator<typename Set::iterator>);
456 static_assert(std::ranges::range<Set>);
457
458 /// @name Construction
459 ///@{
460 Patricia& operator=(const Patricia&) = delete;
461
462 explicit Patricia(size_t page_size = Arena::Default_Page_Size)
463 : arr_arena_(page_size)
464 , br_arena_(page_size) {}
465 Patricia(const Patricia&) = delete;
467 : Patricia() {
468 swap(*this, other);
469 }
470 ///@}
471
472 /// @name Set Operations
473 /// @note These operations do **not** modify their input; they yield a **new** Set.
474 ///@{
475
476 /// Creates a Set with all elements in @p r.
477 template<std::ranges::input_range R>
478 requires std::convertible_to<std::ranges::range_reference_t<R>, D*> [[nodiscard]] Set create(R&& r) {
479 auto v = Vector<Entry>();
480 for (D* d : r)
481 v.emplace_back(Entry{KT::key(d), d});
482 return build(v);
483 }
484
485 [[nodiscard]] Set create(std::initializer_list<D*> list) { return create(View<D*>(list.begin(), list.size())); }
486
487 /// Yields @f$s \cup \{d\}@f$.
488 /// @note @p s comes back unchanged if it already holds an element with @p d's id.
489 [[nodiscard]] Set insert(Set s, D* d) {
490 auto key = KT::key(d);
491
492 if (s.empty()) return Set(d);
493
494 if (auto u = s.isa_uniq()) {
495 auto k = KT::key(u);
496 if (k == key) return s;
497 Entry es[2];
498 if (key < k)
499 es[0] = Entry{key, d}, es[1] = Entry{k, u};
500 else
501 es[0] = Entry{k, u}, es[1] = Entry{key, d};
502 return arr(View<Entry>(es, size_t(2)));
503 }
504
505 if (auto n = s.isa_br()) {
506 if (!match_prefix(key, n->prefix, n->mask)) return join(s, Set(d));
507
508 if (zero_bit(key, n->mask)) {
509 auto l = insert(s.left(), d);
510 return l == s.left() ? s : br(n->prefix, n->mask, l, s.right());
511 }
512
513 auto r = insert(s.right(), d);
514 return r == s.right() ? s : br(n->prefix, n->mask, s.left(), r);
515 }
516
517 auto es = s.entries();
518 auto i = std::ranges::lower_bound(es, key, {}, &Entry::key);
519 if (i != es.end() && i->key == key) return s;
520
521 auto pos = size_t(i - es.begin());
522 auto buf = std::array<Entry, N + 1>();
523 auto o = std::ranges::copy(es.first(pos), buf.begin()).out;
524 *o++ = Entry{key, d};
525 o = std::ranges::copy(es.subspan(pos), o).out;
526 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
527 }
528
529 /// Yields @f$s \setminus \{d\}@f$.
530 [[nodiscard]] Set erase(Set s, D* d) {
531 auto key = KT::key(d);
532
533 if (auto u = s.isa_uniq()) return KT::key(u) == key ? Set() : s;
534
535 if (auto n = s.isa_br()) {
536 if (!match_prefix(key, n->prefix, n->mask)) return s;
537
538 if (zero_bit(key, n->mask)) {
539 auto l = erase(s.left(), d);
540 return l == s.left() ? s : br(n->prefix, n->mask, l, s.right());
541 }
542
543 auto r = erase(s.right(), d);
544 return r == s.right() ? s : br(n->prefix, n->mask, s.left(), r);
545 }
546
547 auto es = s.entries();
548 auto i = std::ranges::find(es, key, &Entry::key);
549 if (i == es.end()) return s;
550
551 auto pos = size_t(i - es.begin());
552 auto buf = std::array<Entry, N>();
553 auto o = std::ranges::copy(es.first(pos), buf.begin()).out;
554 o = std::ranges::copy(es.subspan(pos + 1), o).out;
555 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
556 }
557
558 /// Yields @f$s_1 \cup s_2@f$.
559 [[nodiscard]] Set merge(Set s1, Set s2) {
560 if (s1 == s2 || s2.empty()) return s1;
561 if (s1.empty()) return s2;
562
563 auto n1 = s1.isa_br();
564 auto n2 = s2.isa_br();
565
566 if (!n1 && !n2) return merge_blocks(s1, s2);
567 if (!n1) return insert_all(s2, s1);
568 if (!n2) return insert_all(s1, s2);
569
570 switch (rel(n1, n2)) {
571 case Rel::Same: return br(n1->prefix, n1->mask, merge(s1.left(), s2.left()), merge(s1.right(), s2.right()));
572 case Rel::L1: return br(n1->prefix, n1->mask, merge(s1.left(), s2), s1.right());
573 case Rel::R1: return br(n1->prefix, n1->mask, s1.left(), merge(s1.right(), s2));
574 case Rel::L2: return br(n2->prefix, n2->mask, merge(s1, s2.left()), s2.right());
575 case Rel::R2: return br(n2->prefix, n2->mask, s2.left(), merge(s1, s2.right()));
576 case Rel::None: return join(s1, s2);
577 }
578 std::unreachable();
579 }
580
581 /// Yields @f$s_1 \cap s_2@f$.
582 [[nodiscard]] Set intersect(Set s1, Set s2) {
583 if (s1 == s2) return s1;
584 if (s1.empty() || s2.empty()) return {};
585
586 auto n1 = s1.isa_br();
587 auto n2 = s2.isa_br();
588
589 if (!n1) return filter(s1, s2, true);
590 if (!n2) return filter(s2, s1, true);
591
592 switch (rel(n1, n2)) {
593 case Rel::Same:
594 return br(n1->prefix, n1->mask, intersect(s1.left(), s2.left()), intersect(s1.right(), s2.right()));
595 case Rel::L1: return intersect(s1.left(), s2);
596 case Rel::R1: return intersect(s1.right(), s2);
597 case Rel::L2: return intersect(s1, s2.left());
598 case Rel::R2: return intersect(s1, s2.right());
599 case Rel::None: return {};
600 }
601 std::unreachable();
602 }
603
604 /// Yields @f$s_1 \setminus s_2@f$.
605 [[nodiscard]] Set diff(Set s1, Set s2) {
606 if (s1 == s2) return {};
607 if (s1.empty() || s2.empty()) return s1;
608
609 auto n1 = s1.isa_br();
610 auto n2 = s2.isa_br();
611
612 if (!n1) return filter(s1, s2, false);
613 if (!n2) return erase_all(s1, s2);
614
615 switch (rel(n1, n2)) {
616 case Rel::Same: return br(n1->prefix, n1->mask, diff(s1.left(), s2.left()), diff(s1.right(), s2.right()));
617 case Rel::L1: return br(n1->prefix, n1->mask, diff(s1.left(), s2), s1.right());
618 case Rel::R1: return br(n1->prefix, n1->mask, s1.left(), diff(s1.right(), s2));
619 case Rel::L2: return diff(s1, s2.left());
620 case Rel::R2: return diff(s1, s2.right());
621 case Rel::None: return s1;
622 }
623 std::unreachable();
624 }
625 ///@}
626
627 friend void swap(Patricia& p1, Patricia& p2) noexcept {
628 using std::swap;
629 // clang-format off
630 swap(p1.arr_arena_, p2.arr_arena_);
631 swap(p1.br_arena_, p2.br_arena_);
632 swap(p1.arrs_, p2.arrs_);
633 swap(p1.brs_, p2.brs_);
634 // clang-format on
635 }
636
637private:
638 /// @name Small Cases
639 /// At least one operand is not a `Br` and hence holds at most @p N elements.
640 ///@{
641
642 /// Both do: one linear pass beats inserting them one by one.
643 Set merge_blocks(Set s1, Set s2) {
644 if (auto d = s1.isa_uniq()) return insert(s2, d);
645 if (auto d = s2.isa_uniq()) return insert(s1, d);
646
647 auto es1 = s1.entries();
648 auto es2 = s2.entries();
649 auto buf = std::array<Entry, 2 * N>();
650 auto i1 = es1.begin(), i2 = es2.begin();
651 auto o = buf.begin();
652
653 while (i1 != es1.end() && i2 != es2.end())
654 if (i1->key < i2->key)
655 *o++ = *i1++;
656 else if (i2->key < i1->key)
657 *o++ = *i2++;
658 else
659 *o++ = *i1++, ++i2;
660
661 o = std::ranges::copy(std::ranges::subrange(i1, es1.end()), o).out;
662 o = std::ranges::copy(std::ranges::subrange(i2, es2.end()), o).out;
663 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
664 }
665
666 /// Folds @p s into @p t one element at a time.
667 Set insert_all(Set t, Set s) {
668 if (auto d = s.isa_uniq()) return insert(t, d);
669 for (const auto& e : s.entries())
670 t = insert(t, e.d);
671 return t;
672 }
673
674 Set erase_all(Set t, Set s) {
675 if (auto d = s.isa_uniq()) return erase(t, d);
676 for (const auto& e : s.entries())
677 t = erase(t, e.d);
678 return t;
679 }
680
681 /// The elements of @p s that @p other holds as well - or exactly those it does not, for `!in`.
682 Set filter(Set s, Set other, bool in) {
683 if (auto d = s.isa_uniq()) return other.contains(d) == in ? s : Set();
684
685 auto buf = std::array<Entry, N>();
686 auto o = buf.begin();
687 for (const auto& e : s.entries())
688 if (bool(other.lookup(e.key)) == in) *o++ = e;
689 return make(View<Entry>(buf.data(), size_t(o - buf.begin())));
690 }
691 ///@}
692
693 /// @name Node Construction
694 /// This is where the four flavours and the hash-consing live.
695 ///@{
696
697 /// Sorts @p v and drops the duplicate ids, then hands the result to Patricia::make.
698 Set build(Vector<Entry>& v) {
699 std::ranges::stable_sort(v, {}, &Entry::key);
700 auto rest = std::ranges::unique(v, {}, &Entry::key);
701 return make(View<Entry>(v.data(), size_t(rest.begin() - v.begin())));
702 }
703
704 /// The canonical Set for the sorted, duplicate-free @p es.
705 Set make(View<Entry> es) {
706 if (es.empty()) return {};
707 if (es.size() == 1) return Set(es.front().d);
708 if (es.size() <= N) return arr(es);
709
710 auto m = branching_bit(es.front().key, es.back().key);
711 auto p = mask_of(es.front().key, m);
712 auto mid
713 = size_t(std::ranges::partition_point(es, [m](const Entry& e) { return zero_bit(e.key, m); }) - es.begin());
714 return br(p, m, make(es.first(mid)), make(es.subspan(mid)));
715 }
716
717 /// Joins two Set%s whose id ranges are disjoint.
718 Set join(Set s1, Set s2) {
719 auto p1 = s1.prefix();
720 auto p2 = s2.prefix();
721 auto m = branching_bit(p1, p2);
722 auto p = mask_of(p1, m);
723 return zero_bit(p1, m) ? br(p, m, s1, s2) : br(p, m, s2, s1);
724 }
725
726 Set arr(View<Entry> es) {
727 assert(2 <= es.size() && es.size() <= N);
728 auto state = arr_arena_.state();
729 auto buff = arr_arena_.allocate(sizeof(Arr) + es.size() * sizeof(Entry), alignof(Arr));
730 auto node = new (buff) Arr(hash_entries(es), uint32_t(es.size()));
731 std::uninitialized_copy(es.begin(), es.end(), node->entries);
732 auto [i, ins] = arrs_.emplace(node);
733 if (!ins) arr_arena_.deallocate(state);
734 return Set(*i);
735 }
736
737 /// A branch - unless @p l and @p r together still fit into one array node.
738 Set br(K prefix, K mask, Set l, Set r) {
739 if (l.empty()) return r;
740 if (r.empty()) return l;
741
742 auto size = l.size() + r.size();
743 if (size <= N) { // neither child is a Br, and l's ids all precede r's: concatenate
744 auto buf = std::array<Entry, N>();
745 auto o = l.copy(buf.begin());
746 o = r.copy(o);
747 return arr(View<Entry>(buf.data(), size));
748 }
749
750 auto state = br_arena_.state();
751 auto node = new (br_arena_.allocate<Br>(1)) Br(prefix, mask, size, l.word_, r.word_);
752 auto [i, ins] = brs_.emplace(node);
753 if (!ins) br_arena_.deallocate(state);
754 return Set(*i);
755 }
756 ///@}
757
758 /// @name Hash-Consing
759 /// The id follows from the element, so only the pointers take part.
760 ///@{
761 static size_t hash_entries(View<Entry> es) noexcept {
762 uint64_t h = 0;
763 for (const auto& e : es)
764 h = hash_combine(h, std::bit_cast<uintptr_t>(e.d));
765 return h;
766 }
767
768 struct ArrHash {
769 using is_avalanching = void;
770
771 size_t operator()(const Arr* n) const noexcept { return n->hash; }
772 };
773
774 struct ArrEq {
775 bool operator()(const Arr* n1, const Arr* n2) const noexcept {
776 if (n1->size != n2->size) return false;
777 for (uint32_t i = 0; i != n1->size; ++i)
778 if (n1->entries[i].d != n2->entries[i].d) return false;
779 return true;
780 }
781 };
782
783 struct BrHash {
784 using is_avalanching = void;
785
786 size_t operator()(const Br* n) const noexcept {
787 return ankerl::unordered_dense::hash<std::tuple<K, K, uintptr_t, uintptr_t>>()(
788 {n->prefix, n->mask, n->l, n->r});
789 }
790 };
791
792 struct BrEq {
793 bool operator()(const Br* n1, const Br* n2) const noexcept {
794 // The children are canonical already, so comparing their words settles the whole subtree.
795 return n1->prefix == n2->prefix && n1->mask == n2->mask && n1->l == n2->l && n1->r == n2->r;
796 }
797 };
798
799 template<class T, class H, class E>
800 using Pool = ankerl::unordered_dense::set<const T*, H, E>;
801 ///@}
802
803 // One Arena per node kind, so that rolling a speculative allocation back on a pool hit is always LIFO.
804 Arena arr_arena_;
805 Arena br_arena_;
806 Pool<Arr, ArrHash, ArrEq> arrs_;
807 Pool<Br, BrHash, BrEq> brs_;
808};
809
810} // namespace fe
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:27
Yields the D* in ascending Key::key order.
Definition patricia.h:186
iterator() noexcept=default
iterator & operator++() noexcept
Definition patricia.h:199
bool operator==(const iterator &other) const noexcept
Definition patricia.h:210
std::ptrdiff_t difference_type
Definition patricia.h:189
pointer operator->() const noexcept
Definition patricia.h:197
std::forward_iterator_tag iterator_category
Definition patricia.h:188
iterator operator++(int) noexcept
Definition patricia.h:204
reference operator*() const noexcept
Definition patricia.h:196
An immutable set - really just a tagged pointer, so copy it around freely.
Definition patricia.h:141
size_t size() const noexcept
Definition patricia.h:275
constexpr Set() noexcept=default
The empty set.
bool has_intersection(Set other) const noexcept
Is ?
Definition patricia.h:293
D * max() const noexcept
Largest id - or nullptr.
Definition patricia.h:285
D * min() const noexcept
Smallest id - or nullptr.
Definition patricia.h:284
iterator begin() const noexcept
Definition patricia.h:338
void for_each(auto &&f) const
Like iterating, but without the iterator's path stack.
Definition patricia.h:342
void dot(std::ostream &os) const
Definition patricia.h:379
bool subset_of(Set other) const noexcept
Is ?
Definition patricia.h:316
void dump() const
Definition patricia.h:377
friend std::ostream & operator<<(std::ostream &os, Set s)
Definition patricia.h:452
constexpr bool empty() const noexcept
Definition patricia.h:281
std::ostream & stream(std::ostream &os) const
Definition patricia.h:363
iterator end() const noexcept
Definition patricia.h:339
bool contains(D *d) const noexcept
Definition patricia.h:290
constexpr bool operator==(Set other) const noexcept
Definition patricia.h:358
friend class Patricia
Definition patricia.h:451
Set diff(Set s1, Set s2)
Yields .
Definition patricia.h:605
Set insert(Set s, D *d)
Yields .
Definition patricia.h:489
friend void swap(Patricia &p1, Patricia &p2) noexcept
Definition patricia.h:627
Patricia(size_t page_size=Arena::Default_Page_Size)
Definition patricia.h:462
Patricia(Patricia &&other)
Definition patricia.h:466
Patricia & operator=(const Patricia &)=delete
Set create(std::initializer_list< D * > list)
Definition patricia.h:485
Set create(R &&r)
Creates a Set with all elements in r.
Definition patricia.h:478
Set intersect(Set s1, Set s2)
Yields .
Definition patricia.h:582
Patricia(const Patricia &)=delete
Set merge(Set s1, Set s2)
Yields .
Definition patricia.h:559
Set erase(Set s, D *d)
Yields .
Definition patricia.h:530
constexpr Vector(size_t size, F &&f)
Definition vector.h:33
Definition algo.h:17
Span< const T, N > View
Read-only Span; use Span itself, if you want to write through its elements.
Definition span.h:107
auto lookup(auto &container, const auto &key)
Yields a pointer to the value mapped to key (or the value itself if it is a pointer) or nullptr if no...
Definition container.h:45
constexpr size_t hash_combine(size_t seed, T v) noexcept
Folds v into seed like ankerl::unordered_dense hashes a tuple: one 128-bit multiply,...
Definition hash.h:26
Definition span.h:150