FE 0.17.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
bimap.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4#include <cstddef>
5
6#include <functional>
7#include <stdexcept>
8#include <type_traits>
9#include <utility>
10
11#include <ankerl/unordered_dense.h>
12
13namespace fe {
14
15namespace detail {
16template<class H, class E>
17concept Transparent = requires {
18 typename H::is_transparent;
19 typename E::is_transparent;
20};
21
22template<class K, class T>
23concept NotConvertibleTo = !std::convertible_to<const K&, T>;
24} // namespace detail
25
26/// A bijection between @p L and @p R, kept as one `ankerl::unordered_dense::map` per direction.
27/// Look up through left() and right(); a transparent hasher and equality enable heterogeneous lookup there as well.
28/// Iterating a BiMap yields its `std::pair<L, R>`s in left() order.
29/// @note Every pair is stored once per direction, so keep @p L and @p R cheap to copy - a Sym or a pointer, say.
30/// Otherwise, or if you need a pair's address to survive an insert, use BiNodeMap.
31template<class L,
32 class R,
33 class LHash = ankerl::unordered_dense::hash<L>,
34 class LEq = std::equal_to<L>,
35 class RHash = ankerl::unordered_dense::hash<R>,
36 class REq = std::equal_to<R>>
37class BiMap {
38public:
39 using Left = ankerl::unordered_dense::map<L, R, LHash, LEq>;
40 using Right = ankerl::unordered_dense::map<R, L, RHash, REq>;
41
42 /// @name Getters
43 ///@{
44 [[nodiscard]] const Left& left() const noexcept { return l2r_; }
45 [[nodiscard]] const Right& right() const noexcept { return r2l_; }
46 [[nodiscard]] size_t size() const noexcept { return l2r_.size(); }
47 [[nodiscard]] bool empty() const noexcept { return l2r_.empty(); }
48 [[nodiscard]] auto begin() const noexcept { return l2r_.begin(); }
49 [[nodiscard]] auto end() const noexcept { return l2r_.end(); }
50 ///@}
51
52 /// @name Lookup
53 /// at_left() and at_right() throw `std::out_of_range` if the key is unbound.
54 ///@{
55 [[nodiscard]] bool contains_left(const auto& l) const { return l2r_.contains(l); }
56 [[nodiscard]] bool contains_right(const auto& r) const { return r2l_.contains(r); }
57 [[nodiscard]] const R& at_left(const auto& l) const { return l2r_.at(l); }
58 [[nodiscard]] const L& at_right(const auto& r) const { return r2l_.at(r); }
59 ///@}
60
61 /// @name Modifiers
62 /// They take their arguments by value, so a reference into this BiMap cannot dangle halfway through.
63 ///@{
64
65 /// Binds @p l to @p r unless either is already bound; yields whether it did.
66 bool insert(L l, R r) {
67 auto [i, ins] = l2r_.try_emplace(l, r);
68 return ins && link(i, std::move(l), std::move(r));
69 }
70
71 /// Binds @p l to @p r after unbinding whatever either was bound to.
72 void insert_or_assign(L l, R r) {
73 erase_left(l);
74 erase_right(r);
75 link(l2r_.emplace(l, r).first, std::move(l), std::move(r));
76 }
77
78 /// Unbinds @p l and its partner; yields whether @p l was bound.
79 bool erase_left(const auto& l) {
80 auto i = l2r_.find(l);
81 if (i == l2r_.end()) return false;
82 r2l_.erase(i->second);
83 l2r_.erase(i);
84 return true;
85 }
86
87 /// Unbinds @p r and its partner; yields whether @p r was bound.
88 bool erase_right(const auto& r) {
89 auto i = r2l_.find(r);
90 if (i == r2l_.end()) return false;
91 l2r_.erase(i->second);
92 r2l_.erase(i);
93 return true;
94 }
95
96 void reserve(size_t n) {
97 l2r_.reserve(n);
98 r2l_.reserve(n);
99 }
100
101 void clear() noexcept {
102 l2r_.clear();
103 r2l_.clear();
104 }
105 ///@}
106
107 friend bool operator==(const BiMap& a, const BiMap& b) { return a.l2r_ == b.l2r_; }
108
109private:
110 bool link(typename Left::iterator i, L&& l, R&& r) {
111 try {
112 if (r2l_.try_emplace(std::move(r), std::move(l)).second) return true;
113 } catch (...) {
114 l2r_.erase(i);
115 throw;
116 }
117 l2r_.erase(i);
118 return false;
119 }
120
121 Left l2r_;
122 Right r2l_;
123};
124
125/// A bijection between @p L and @p R that stores every pair once, in an `ankerl::unordered_dense::segmented_map`.
126/// The right side is a set of pointers to those pairs, hashed and compared by their @p R.
127/// An insert never moves a pair, and neither does moving the BiNodeMap itself.
128/// An erase, however, moves the last pair into the freed slot, as it does in any `ankerl::unordered_dense` map.
129/// Its interface is that of BiMap, except that right() has no map to yield; find_right() takes its place.
130template<class L,
131 class R,
132 class LHash = ankerl::unordered_dense::hash<L>,
133 class LEq = std::equal_to<L>,
134 class RHash = ankerl::unordered_dense::hash<R>,
135 class REq = std::equal_to<R>>
137public:
138 using Left = ankerl::unordered_dense::segmented_map<L, R, LHash, LEq>;
139 using value_type = typename Left::value_type;
140
141private:
142 using Ptr = const value_type*;
143
144 struct Hash {
145 using is_transparent = void;
146 using is_avalanching = std::bool_constant<ankerl::unordered_dense::hash_is_avalanching_v<RHash>>;
147
148 size_t operator()(Ptr p) const { return hash(p->second); }
149 template<detail::NotConvertibleTo<Ptr> K>
150 size_t operator()(const K& r) const {
151 return hash(r);
152 }
153
154 RHash hash;
155 };
156
157 struct Eq {
158 using is_transparent = void;
159
160 bool operator()(Ptr a, Ptr b) const { return eq(a->second, b->second); }
161 template<detail::NotConvertibleTo<Ptr> K>
162 bool operator()(const K& r, Ptr b) const {
163 return eq(r, b->second);
164 }
165
166 REq eq;
167 };
168
169public:
170 BiNodeMap() = default;
171 BiNodeMap(const BiNodeMap& other)
172 : l2r_(other.l2r_) {
173 r2l_.reserve(l2r_.size());
174 for (const auto& p : l2r_)
175 r2l_.emplace(&p);
176 }
177 BiNodeMap(BiNodeMap&&) noexcept = default;
178
179 BiNodeMap& operator=(const BiNodeMap& other) {
180 *this = BiNodeMap(other);
181 return *this;
182 }
183 BiNodeMap& operator=(BiNodeMap&&) noexcept = default;
184
185 /// @name Getters
186 ///@{
187 [[nodiscard]] const Left& left() const noexcept { return l2r_; }
188 [[nodiscard]] size_t size() const noexcept { return l2r_.size(); }
189 [[nodiscard]] bool empty() const noexcept { return l2r_.empty(); }
190 [[nodiscard]] auto begin() const noexcept { return l2r_.begin(); }
191 [[nodiscard]] auto end() const noexcept { return l2r_.end(); }
192 ///@}
193
194 /// @name Lookup
195 /// at_left() and at_right() throw `std::out_of_range` if the key is unbound.
196 ///@{
197 [[nodiscard]] bool contains_left(const auto& l) const { return l2r_.contains(l); }
198 [[nodiscard]] bool contains_right(const auto& r) const { return find_right(r) != nullptr; }
199 [[nodiscard]] const R& at_left(const auto& l) const { return l2r_.at(l); }
200 [[nodiscard]] const L& at_right(const auto& r) const {
201 if (auto p = find_right(r)) return p->first;
202 throw std::out_of_range("fe::BiNodeMap::at_right");
203 }
204
205 /// Yields the pair whose right side is @p r, or `nullptr`.
206 template<class K>
207 [[nodiscard]] Ptr find_right(const K& r) const {
208 if constexpr (std::same_as<K, R> || detail::Transparent<RHash, REq>) {
209 auto i = r2l_.find(r);
210 return i != r2l_.end() ? *i : nullptr;
211 } else {
212 const R& key = r;
213 return find_right(key);
214 }
215 }
216 ///@}
217
218 /// @name Modifiers
219 /// They take their arguments by value, so a reference into this BiNodeMap cannot dangle halfway through.
220 ///@{
221
222 /// Binds @p l to @p r unless either is already bound; yields whether it did.
223 bool insert(L l, R r) {
224 auto [i, ins] = l2r_.try_emplace(std::move(l), std::move(r));
225 return ins && index(i);
226 }
227
228 /// Binds @p l to @p r after unbinding whatever either was bound to.
229 void insert_or_assign(L l, R r) {
230 erase_left(l);
231 erase_right(r);
232 index(l2r_.try_emplace(std::move(l), std::move(r)).first);
233 }
234
235 /// Unbinds @p l and its partner; yields whether @p l was bound.
236 bool erase_left(const auto& l) {
237 auto i = l2r_.find(l);
238 if (i == l2r_.end()) return false;
239 erase(&*i);
240 return true;
241 }
242
243 /// Unbinds @p r and its partner; yields whether @p r was bound.
244 bool erase_right(const auto& r) {
245 auto p = find_right(r);
246 if (!p) return false;
247 erase(p);
248 return true;
249 }
250
251 void reserve(size_t n) {
252 l2r_.reserve(n);
253 r2l_.reserve(n);
254 }
255
256 void clear() noexcept {
257 r2l_.clear();
258 l2r_.clear();
259 }
260 ///@}
261
262 friend bool operator==(const BiNodeMap& a, const BiNodeMap& b) { return a.l2r_ == b.l2r_; }
263
264private:
265 bool index(typename Left::iterator i) {
266 try {
267 if (r2l_.emplace(&*i).second) return true;
268 } catch (...) {
269 l2r_.erase(i);
270 throw;
271 }
272 l2r_.erase(i);
273 return false;
274 }
275
276 void erase(Ptr p) {
277 Ptr back = &l2r_.values().back();
278 r2l_.erase(p);
279 auto j = p != back ? r2l_.find(back) : r2l_.end();
280 l2r_.erase(p->first);
281 // The former back now lives at p and keeps its R, so only the pointer changes, not its hash.
282 if (j != r2l_.end()) const_cast<Ptr&>(*j) = p;
283 }
284
285 Left l2r_;
286 ankerl::unordered_dense::set<Ptr, Hash, Eq> r2l_;
287};
288
289} // namespace fe
A bijection between L and R, kept as one ankerl::unordered_dense::map per direction.
Definition bimap.h:37
ankerl::unordered_dense::map< R, L, RHash, REq > Right
Definition bimap.h:40
auto end() const noexcept
Definition bimap.h:49
bool contains_right(const auto &r) const
Definition bimap.h:56
void clear() noexcept
Definition bimap.h:101
size_t size() const noexcept
Definition bimap.h:46
bool empty() const noexcept
Definition bimap.h:47
const L & at_right(const auto &r) const
Definition bimap.h:58
bool contains_left(const auto &l) const
Definition bimap.h:55
bool erase_right(const auto &r)
Unbinds r and its partner; yields whether r was bound.
Definition bimap.h:88
const R & at_left(const auto &l) const
Definition bimap.h:57
bool insert(L l, R r)
Binds l to r unless either is already bound; yields whether it did.
Definition bimap.h:66
void reserve(size_t n)
Definition bimap.h:96
ankerl::unordered_dense::map< L, R, LHash, LEq > Left
Definition bimap.h:39
void insert_or_assign(L l, R r)
Binds l to r after unbinding whatever either was bound to.
Definition bimap.h:72
auto begin() const noexcept
Definition bimap.h:48
const Left & left() const noexcept
Definition bimap.h:44
friend bool operator==(const BiMap &a, const BiMap &b)
Definition bimap.h:107
bool erase_left(const auto &l)
Unbinds l and its partner; yields whether l was bound.
Definition bimap.h:79
const Right & right() const noexcept
Definition bimap.h:45
void clear() noexcept
Definition bimap.h:256
auto end() const noexcept
Definition bimap.h:191
bool erase_left(const auto &l)
Unbinds l and its partner; yields whether l was bound.
Definition bimap.h:236
bool contains_left(const auto &l) const
Definition bimap.h:197
const Left & left() const noexcept
Definition bimap.h:187
bool contains_right(const auto &r) const
Definition bimap.h:198
BiNodeMap & operator=(BiNodeMap &&) noexcept=default
bool empty() const noexcept
Definition bimap.h:189
void reserve(size_t n)
Definition bimap.h:251
auto begin() const noexcept
Definition bimap.h:190
void insert_or_assign(L l, R r)
Binds l to r after unbinding whatever either was bound to.
Definition bimap.h:229
BiNodeMap(const BiNodeMap &other)
Definition bimap.h:171
const L & at_right(const auto &r) const
Definition bimap.h:200
bool insert(L l, R r)
Binds l to r unless either is already bound; yields whether it did.
Definition bimap.h:223
typename Left::value_type value_type
Definition bimap.h:139
BiNodeMap()=default
friend bool operator==(const BiNodeMap &a, const BiNodeMap &b)
Definition bimap.h:262
size_t size() const noexcept
Definition bimap.h:188
ankerl::unordered_dense::segmented_map< L, R, LHash, LEq > Left
Definition bimap.h:138
const R & at_left(const auto &l) const
Definition bimap.h:199
Ptr find_right(const K &r) const
Yields the pair whose right side is r, or nullptr.
Definition bimap.h:207
BiNodeMap(BiNodeMap &&) noexcept=default
bool erase_right(const auto &r)
Unbinds r and its partner; yields whether r was bound.
Definition bimap.h:244
Definition algo.h:17