| // Copyright 2017-2020 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef VERIBLE_COMMON_UTIL_MAP_TREE_H_ |
| #define VERIBLE_COMMON_UTIL_MAP_TREE_H_ |
| |
| #include <cstddef> |
| #include <functional> |
| #include <iostream> |
| #include <map> |
| #include <utility> |
| |
| #include "common/util/logging.h" |
| #include "common/util/spacer.h" |
| |
| namespace verible { |
| |
| // MapTree is a hierarchical tree representation of values, where branches are |
| // associated with keys. |
| // This is one implementation of a 'trie' or 'prefix tree' data structure. |
| // There are minimal constraints on the key and value types: |
| // K is the key type of each node. |
| // K needs to be copy-able. |
| // V is the type of data stored at each node. |
| // V only needs to be move-able, and need not be copy-able. |
| // Copy-ability is only needed when deep-copying the tree. |
| // KeyComp is the comparator for K for ordering. |
| // KeyComp can be a heterogenous lookup comparator (C++14). |
| // |
| // Key-value pairs are co-located together, and are iterator-stable, meaning |
| // that insertion/deletion operations do not invalidate existing iterators |
| // or existing pointers to other nodes in the same family tree. |
| // Insertion/deletion/search operations are O(lg N). |
| // |
| // Nodes maintain links to their parent (except the root node), so upwards |
| // navigation toward the root is always possible. |
| // |
| // Navigation: |
| // |
| // MapTree (parent) <----------------. |
| // | | |
| // | .Find(key) -> iterator | |
| // | | .Parent() -> MapTree* |
| // v key_value_type (pair) | |
| // +=============+==================+ / |
| // | .first: | .second: |/ |
| // | key | MapTree (child) | |
| // | key_type | node_value_type | |
| // +=============+==================+ |
| // ^ | |
| // | | |
| // \---------------/ |
| // .Key() -> key_type* |
| // |
| // Example applications: |
| // * Dictionaries, where K is a single char. |
| // * Hierarchical symbol tables, where K is string-like. |
| // * Lookup operations could use upward search. |
| // * Subcommand menus, where K is string-like. |
| // * File-system like structures, with string-like K. |
| // * Navigation through parent directories uses upward links. |
| // |
| template <typename K, typename V, typename KeyComp = std::less<K>> |
| class MapTree { |
| using this_type = MapTree<K, V, KeyComp>; |
| |
| // Self-recursive type that holds subtrees. |
| // A std::map is chosen for key-value co-location and iterator stability. |
| // We chose std::map over std::set to allow values to be mutable, while keys |
| // remain const. |
| using subtrees_type = std::map<K, this_type, KeyComp>; |
| |
| public: |
| using key_type = K; |
| using node_value_type = V; |
| using key_value_type = typename subtrees_type::value_type; // pair |
| using iterator = typename subtrees_type::iterator; |
| using const_iterator = typename subtrees_type::const_iterator; |
| |
| MapTree() = default; |
| |
| // deep-copy, requires node_value_type to be copy-able. |
| MapTree(const MapTree &other) |
| : node_value_(other.node_value_), |
| subtrees_(other.subtrees_), |
| // new copy is disconnected from original parent and is a new root |
| parent_(nullptr) { |
| Relink(); |
| } |
| |
| // move (with relink) |
| MapTree(MapTree &&other) noexcept |
| : node_value_(std::move(other.node_value_)), |
| subtrees_(std::move(other.subtrees_)), |
| // Retain existing parent. |
| parent_(other.parent_) { |
| Relink(); |
| } |
| |
| // TODO(fangism): implement assignments as needed |
| MapTree &operator=(const MapTree &) = delete; |
| MapTree &operator=(MapTree &&) = delete; |
| |
| // Recursively initialize trees (copy node value). |
| // Example: |
| // using M = MapTree<...>; |
| // using P = M::key_value_type; // pair |
| // M tree( |
| // value0, |
| // P{key1, M(value1, |
| // P{key2, M(value2)}, |
| // P{key3, M(value3)})}, |
| // P{key4, M(value4, |
| // P{key5, M(value5)}, |
| // P{key6, M(value6)})} |
| // ); |
| template <typename... Args> |
| explicit MapTree(const node_value_type &v, Args &&...args) |
| : node_value_(v), subtrees_() { |
| EmplacePairs(std::forward<Args>(args)...); |
| } |
| |
| // Recursively initialize trees (move node value). |
| // See example above, using node_value_type copy. |
| template <typename... Args> |
| explicit MapTree(node_value_type &&v, Args &&...args) |
| : node_value_(std::move(v)), subtrees_() { |
| EmplacePairs(std::forward<Args>(args)...); |
| } |
| |
| ~MapTree() { CHECK(CheckIntegrity()); } |
| |
| void swap(this_type &other) { |
| std::swap(node_value_, other.node_value_); |
| subtrees_.swap(other.subtrees_); |
| Relink(); |
| other.Relink(); |
| } |
| |
| // Validation |
| |
| // Ensure parent-child mutual linkage invariant. |
| // This property should hold after any mutating operation. |
| bool CheckIntegrity() const { |
| for (const auto &node : *this) { |
| CHECK_EQ(node.second.Parent(), this) |
| << "Inconsistency: child's parent does not point back to this node!"; |
| if (!node.second.CheckIntegrity()) return false; |
| } |
| return true; |
| } |
| |
| // Insertion |
| |
| // Inserts a node at 'key' if it doesn't already exist. |
| // 'args' are type V's constructor arguments, forwarded. |
| // Returns (iterator, bool), where iterator points to the element at 'key' |
| // whether or not it was newly inserted or already there, and true to indicate |
| // that it was newly created, false if an entry already existed at that key. |
| template <typename... Args> |
| std::pair<iterator, bool> TryEmplace(const key_type &key, Args &&...args) { |
| const auto p = subtrees_.emplace( |
| key_value_type{key, node_value_type(std::forward<Args>(args)...)}); |
| if (p.second) { |
| // Link child to parent. |
| p.first->second.parent_ = this; |
| } |
| return p; |
| } |
| |
| // No-op base case for variadic EmplacePairs(). |
| void EmplacePairs() const {} |
| |
| // Appends one or more sub-trees at this level. |
| // Variadic template handles one argument at a time. |
| // F and Args should each be key-value pairs (key_value_type). |
| // If there are duplicate keys, only the first of each key will be taken, |
| // while the other duplicates will be dropped. |
| template <typename F, typename... Args> |
| void EmplacePairs(F &&first, Args &&...args) { |
| const auto p = subtrees_.emplace(std::forward<F>(first)); |
| if (p.second) { |
| // Link child to parent. |
| p.first->second.parent_ = this; |
| } |
| // Emplace the remaining items. |
| EmplacePairs(std::forward<Args>(args)...); |
| } |
| |
| // TODO(fangism): TryEmplaceHint(), like map::emplace_hint. |
| |
| // Erasure |
| // TODO(fangism): void Erase(key); |
| |
| // Iteration/Navigation |
| |
| this_type *Parent() { return parent_; } |
| const this_type *Parent() const { return parent_; } |
| |
| iterator begin() { return subtrees_.begin(); } |
| const_iterator begin() const { return subtrees_.begin(); } |
| iterator end() { return subtrees_.end(); } |
| const_iterator end() const { return subtrees_.end(); } |
| |
| bool is_leaf() const { return Children().empty(); } |
| |
| size_t NumAncestors() const { |
| size_t depth = 0; |
| for (const this_type *iter = Parent(); iter != nullptr; |
| iter = iter->Parent()) { |
| ++depth; |
| } |
| return depth; |
| } |
| |
| // Returns true if 'other' is an ancestor of this node, in other words, |
| // this node is descended from 'other'. |
| // This method could have been named IsDescendedFrom(). |
| // nullptr is never considered an ancestor of any node. |
| // 'this' node is not considered an ancestor of itself. |
| bool HasAncestor(const this_type *other) const { |
| if (other == nullptr) return false; |
| for (const this_type *iter = Parent(); iter != nullptr; |
| iter = iter->Parent()) { |
| if (iter == other) return true; |
| } |
| return false; |
| } |
| |
| // Returns pointer to the tree root, the greatest ancestor of this node. |
| const this_type *Root() const { |
| const this_type *node = this; |
| while (node->Parent() != nullptr) node = node->Parent(); |
| return node; |
| } |
| |
| // Returns pointer to the key-value pair of which this node is the value. |
| // Root nodes return nullptr because they have no associated key. |
| const key_value_type *KeyValuePair() const { |
| // Note: The use of offsetof below is technically undefined until C++17 |
| // because std::pair is not a standard layout type. However, all compilers |
| // currently provide well-defined behavior as an extension (which is |
| // demonstrated since constexpr evaluation must diagnose all undefined |
| // behavior). However, GCC and Clang also warn about this use of offsetof, |
| // which must be suppressed. |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Winvalid-offsetof" |
| #pragma GCC diagnostic ignored "-Warray-bounds" |
| #endif |
| // Compute offset of member &key_value_type::second (built-in function). |
| static constexpr auto value_offset = offsetof(key_value_type, second); |
| if (Parent() == nullptr) return nullptr; // Root node has no key |
| // All non-root nodes belong to a parent's map, and thus are |
| // pair-co-located with a key. |
| // Use pointer arithmetic to recover location of the key-value pair to which |
| // this node value belongs. |
| return reinterpret_cast<const key_value_type *>( |
| reinterpret_cast<const char *>(this) - value_offset); |
| #ifdef __GNUC__ |
| #pragma GCC diagnostic pop |
| #endif |
| // TODO(fangism): factor this out into member_offsetof() template function. |
| } |
| |
| // Returns the key associated with this node (if this is not a root), |
| // otherwise return nullptr. |
| const key_type *Key() const { |
| if (Parent() == nullptr) return nullptr; // Root node has no key |
| return &KeyValuePair()->first; |
| } |
| |
| // Access |
| |
| const subtrees_type &Children() const { return subtrees_; } |
| |
| node_value_type &Value() { return node_value_; } |
| const node_value_type &Value() const { return node_value_; } |
| |
| // Search |
| |
| // Returns an iterator located at 'key' or end() if not found. |
| // O(lg N), same as underlying map type. |
| template <typename AnyKey> |
| iterator Find(AnyKey &&key) { |
| // Forward to underlying map::find, enabling heterogenous lookup. |
| return subtrees_.find(std::forward<AnyKey>(key)); |
| } |
| |
| // Returns a const_iterator located at 'key' or end() if not found. |
| // O(lg N), same as underlying map type. |
| template <typename AnyKey> |
| const_iterator Find(AnyKey &&key) const { |
| // Forward to underlying map::find, enabling heterogenous lookup. |
| return subtrees_.find(std::forward<AnyKey>(key)); |
| } |
| |
| // Traversals |
| // |
| // Function-application traversals' variants: |
| // * Apply in pre-order vs. post-order. |
| // * Apply to attached value at each node vs. the whole node itself. |
| // Whole node gives access to immediate children (and subtree). |
| // * Apply function is const vs. mutating. |
| // All combinations of the above are provided. |
| // Note that keys can never be mutated. |
| |
| // Applies function 'f' to all nodes in this tree in a pre-order traversal. |
| // Children are visited in key-order. |
| void ApplyPreOrder(const std::function<void(const this_type &)> &f) const { |
| f(*this); |
| for (const auto &child : *this) { |
| child.second.ApplyPreOrder(f); |
| } |
| } |
| void ApplyPreOrder(const std::function<void(this_type &)> &f) { |
| f(*this); |
| for (auto &child : *this) { |
| child.second.ApplyPreOrder(f); |
| } |
| } |
| |
| // This variant of ApplyPreOrder expects a function on the underlying |
| // node_value_type (const&). |
| void ApplyPreOrder( |
| const std::function<void(const node_value_type &)> &f) const { |
| ApplyPreOrder([&f](const this_type &t) { f(t.Value()); }); |
| } |
| void ApplyPreOrder(const std::function<void(node_value_type &)> &f) { |
| ApplyPreOrder([&f](this_type &t) { f(t.Value()); }); |
| } |
| |
| // Applies function 'f' to all nodes in this tree in a post-order traversal. |
| // Children are visited in key-order. |
| void ApplyPostOrder(const std::function<void(const this_type &)> &f) const { |
| for (const auto &child : *this) { |
| child.second.ApplyPostOrder(f); |
| } |
| f(*this); |
| } |
| void ApplyPostOrder(const std::function<void(this_type &)> &f) { |
| for (auto &child : *this) { |
| child.second.ApplyPostOrder(f); |
| } |
| f(*this); |
| } |
| |
| // This variant of ApplyPostOrder expects a function on the underlying |
| // node_value_type (const&). |
| void ApplyPostOrder( |
| const std::function<void(const node_value_type &)> &f) const { |
| ApplyPostOrder([&f](const this_type &t) { f(t.Value()); }); |
| } |
| void ApplyPostOrder(const std::function<void(node_value_type &)> &f) { |
| ApplyPostOrder([&f](this_type &t) { f(t.Value()); }); |
| } |
| |
| // Transforms |
| |
| // Printing and formatting |
| |
| // Pretty-print tree, using a custom node_value printer function. |
| // Keys are printed using operator<<. |
| std::ostream &PrintTree(std::ostream &stream, |
| const std::function<std::ostream &( |
| std::ostream &, const node_value_type &, |
| size_t value_indent)> &printer, |
| size_t indent = 0) const { |
| // Indentation will be printed before the key, not here. |
| printer(stream << "{ (", Value(), indent) << ')'; |
| if (Children().empty()) { |
| stream << " }"; |
| } else { |
| stream << '\n'; |
| for (const auto &child : Children()) { |
| stream << Spacer(indent + 2) << child.first << ": "; |
| child.second.PrintTree(stream, printer, indent + 2) << '\n'; |
| } |
| stream << Spacer(indent) << '}'; |
| } |
| return stream; |
| } |
| |
| // Pretty-print tree, using the default stream printer, which requires that |
| // operator<<(std::ostream&, const node_value_type&) is defined. |
| std::ostream &PrintTree(std::ostream &stream, size_t indent = 0) const { |
| return PrintTree( |
| stream, |
| [](std::ostream &s, const node_value_type &v, |
| size_t unused_indent) -> std::ostream & { return s << v; }, |
| indent); |
| } |
| |
| private: // methods |
| // Establish parent-child links. |
| void Relink() { |
| for (auto &subtree : subtrees_) { |
| subtree.second.parent_ = this; |
| } |
| } |
| |
| private: // data |
| // Singular value stored at this node. |
| node_value_type node_value_; |
| |
| // Collection of subtrees |
| subtrees_type subtrees_; |
| |
| // Pointer to parent node, or nullptr if this is a root node. |
| this_type *parent_ = nullptr; |
| }; |
| |
| } // namespace verible |
| |
| #endif // VERIBLE_COMMON_UTIL_MAP_TREE_H_ |