--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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 LIBSPIRV_UTIL_MOVE_TO_FRONT_H_
+#define LIBSPIRV_UTIL_MOVE_TO_FRONT_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <iomanip>
+#include <iostream>
+#include <ostream>
+#include <sstream>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace spvutils {
+
+// Log(n) move-to-front implementation. Implements the following functions:
+// Insert - pushes value to the front of the mtf sequence
+// (only unique values allowed).
+// Remove - remove value from the sequence.
+// ValueFromRank - access value by its 1-indexed rank in the sequence.
+// RankFromValue - get the rank of the given value in the sequence.
+// Accessing a value with ValueFromRank or RankFromValue moves the value to the
+// front of the sequence (rank of 1).
+//
+// The implementation is based on an AVL-based order statistic tree. The tree
+// is ordered by timestamps issued when values are inserted or accessed (recent
+// values go to the left side of the tree, old values are gradually rotated to
+// the right side).
+//
+// Terminology
+// rank: 1-indexed rank showing how recently the value was inserted or accessed.
+// node: handle used internally to access node data.
+// size: size of the subtree of a node (including the node).
+// height: distance from a node to the farthest leaf.
+template <typename Val>
+class MoveToFront {
+ public:
+ explicit MoveToFront(size_t reserve_capacity = 128) {
+ nodes_.reserve(reserve_capacity);
+
+ // Create NIL node.
+ nodes_.emplace_back(Node());
+ }
+
+ virtual ~MoveToFront() {}
+
+ // Inserts value in the move-to-front sequence. Does nothing if the value is
+ // already in the sequence. Returns true if insertion was successful.
+ // The inserted value is placed at the front of the sequence (rank 1).
+ bool Insert(const Val& value);
+
+ // Removes value from move-to-front sequence. Returns false iff the value
+ // was not found.
+ bool Remove(const Val& value);
+
+ // Computes 1-indexed rank of value in the move-to-front sequence and moves
+ // the value to the front. Example:
+ // Before the call: 4 8 2 1 7
+ // RankFromValue(8) returns 1
+ // After the call: 8 4 2 1 7
+ // Returns true iff the value was found in the sequence.
+ bool RankFromValue(const Val& value, size_t* rank);
+
+ // Returns value corresponding to a 1-indexed rank in the move-to-front
+ // sequence and moves the value to the front. Example:
+ // Before the call: 4 8 2 1 7
+ // ValueFromRank(1) returns 8
+ // After the call: 8 4 2 1 7
+ // Returns true iff the rank is within bounds [1, GetSize()].
+ bool ValueFromRank(size_t rank, Val* value);
+
+ // Returns the number of elements in the move-to-front sequence.
+ size_t GetSize() const {
+ return SizeOf(root_);
+ }
+
+ protected:
+ // Internal tree data structure uses handles instead of pointers. Leaves and
+ // root parent reference a singleton under handle 0. Although dereferencing
+ // a null pointer is not possible, inappropriate access to handle 0 would
+ // cause an assertion. Handles are not garbage collected if value was deprecated
+ // with DeprecateValue(). But handles are recycled when a node is repositioned.
+
+ // Internal tree data structure node.
+ struct Node {
+ // Timestamp from a logical clock which updates every time the element is
+ // accessed through ValueFromRank or RankFromValue.
+ uint32_t timestamp = 0;
+ // The size of the node's subtree, including the node.
+ // SizeOf(LeftOf(node)) + SizeOf(RightOf(node)) + 1.
+ uint32_t size = 0;
+ // Handles to connected nodes.
+ uint32_t left = 0;
+ uint32_t right = 0;
+ uint32_t parent = 0;
+ // Distance to the farthest leaf.
+ // Leaves have height 0, real nodes at least 1.
+ uint32_t height = 0;
+ // Stored value.
+ Val value = Val();
+ };
+
+ // Creates node and sets correct values. Non-NIL nodes should be created only
+ // through this function. If the node with this value has been created previously
+ // and since orphaned, reuses the old node instead of creating a new one.
+ uint32_t CreateNode(uint32_t timestamp, const Val& value) {
+ uint32_t handle = static_cast<uint32_t>(nodes_.size());
+ const auto result = value_to_node_.emplace(value, handle);
+ if (result.second) {
+ // Create new node.
+ nodes_.emplace_back(Node());
+ Node& node = nodes_.back();
+ node.timestamp = timestamp;
+ node.value = value;
+ node.size = 1;
+ // Non-NIL nodes start with height 1 because their NIL children are leaves.
+ node.height = 1;
+ } else {
+ // Reuse old node.
+ handle = result.first->second;
+ assert(!IsInTree(handle));
+ assert(ValueOf(handle) == value);
+ assert(SizeOf(handle) == 1);
+ assert(HeightOf(handle) == 1);
+ MutableTimestampOf(handle) = timestamp;
+ }
+
+ return handle;
+ }
+
+ // Node accessor methods. Naming is designed to be similar to natural
+ // language as these functions tend to be used in sequences, for example:
+ // ParentOf(LeftestDescendentOf(RightOf(node)))
+
+ // Returns value of the node referenced by |handle|.
+ Val ValueOf(uint32_t node) const {
+ return nodes_.at(node).value;
+ }
+
+ // Returns left child of |node|.
+ uint32_t LeftOf(uint32_t node) const {
+ return nodes_.at(node).left;
+ }
+
+ // Returns right child of |node|.
+ uint32_t RightOf(uint32_t node) const {
+ return nodes_.at(node).right;
+ }
+
+ // Returns parent of |node|.
+ uint32_t ParentOf(uint32_t node) const {
+ return nodes_.at(node).parent;
+ }
+
+ // Returns timestamp of |node|.
+ uint32_t TimestampOf(uint32_t node) const {
+ assert(node);
+ return nodes_.at(node).timestamp;
+ }
+
+ // Returns size of |node|.
+ uint32_t SizeOf(uint32_t node) const {
+ return nodes_.at(node).size;
+ }
+
+ // Returns height of |node|.
+ uint32_t HeightOf(uint32_t node) const {
+ return nodes_.at(node).height;
+ }
+
+ // Returns mutable reference to value of |node|.
+ Val& MutableValueOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).value;
+ }
+
+ // Returns mutable reference to handle of left child of |node|.
+ uint32_t& MutableLeftOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).left;
+ }
+
+ // Returns mutable reference to handle of right child of |node|.
+ uint32_t& MutableRightOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).right;
+ }
+
+ // Returns mutable reference to handle of parent of |node|.
+ uint32_t& MutableParentOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).parent;
+ }
+
+ // Returns mutable reference to timestamp of |node|.
+ uint32_t& MutableTimestampOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).timestamp;
+ }
+
+ // Returns mutable reference to size of |node|.
+ uint32_t& MutableSizeOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).size;
+ }
+
+ // Returns mutable reference to height of |node|.
+ uint32_t& MutableHeightOf(uint32_t node) {
+ assert(node);
+ return nodes_.at(node).height;
+ }
+
+ // Returns true iff |node| is left child of its parent.
+ bool IsLeftChild(uint32_t node) const {
+ assert(node);
+ return LeftOf(ParentOf(node)) == node;
+ }
+
+ // Returns true iff |node| is right child of its parent.
+ bool IsRightChild(uint32_t node) const {
+ assert(node);
+ return RightOf(ParentOf(node)) == node;
+ }
+
+ // Returns true iff |node| has no relatives.
+ bool IsOrphan(uint32_t node) const {
+ assert(node);
+ return !ParentOf(node) && !LeftOf(node) && !RightOf(node);
+ }
+
+ // Returns true iff |node| is in the tree.
+ bool IsInTree(uint32_t node) const {
+ assert(node);
+ return node == root_ || !IsOrphan(node);
+ }
+
+ // Returns the height difference between right and left subtrees.
+ int BalanceOf(uint32_t node) const {
+ return int(HeightOf(RightOf(node))) - int(HeightOf(LeftOf(node)));
+ }
+
+ // Updates size and height of the node, assuming that the children have
+ // correct values.
+ void UpdateNode(uint32_t node);
+
+ // Returns the most LeftOf(LeftOf(... descendent which is not leaf.
+ uint32_t LeftestDescendantOf(uint32_t node) const {
+ uint32_t parent = 0;
+ while (node) {
+ parent = node;
+ node = LeftOf(node);
+ }
+ return parent;
+ }
+
+ // Returns the most RightOf(RightOf(... descendent which is not leaf.
+ uint32_t RightestDescendantOf(uint32_t node) const {
+ uint32_t parent = 0;
+ while (node) {
+ parent = node;
+ node = RightOf(node);
+ }
+ return parent;
+ }
+
+ // Inserts node in the tree. The node must be an orphan.
+ void InsertNode(uint32_t node);
+
+ // Removes node from the tree. May change value_to_node_ if removal uses a
+ // scapegoat. Returns the removed (orphaned) handle for recycling. The
+ // returned handle may not be equal to |node| if scapegoat was used.
+ uint32_t RemoveNode(uint32_t node);
+
+ // Rotates |node| left, reassigns all connections and returns the node
+ // which takes place of the |node|.
+ uint32_t RotateLeft(const uint32_t node);
+
+ // Rotates |node| right, reassigns all connections and returns the node
+ // which takes place of the |node|.
+ uint32_t RotateRight(const uint32_t node);
+
+ // Root node handle. The tree is empty if root_ is 0.
+ uint32_t root_ = 0;
+
+ // Incremented counters for next timestamp and value.
+ uint32_t next_timestamp_ = 1;
+
+ // Holds all tree nodes. Indices of this vector are node handles.
+ std::vector<Node> nodes_;
+
+ // Maps ids to node handles.
+ std::unordered_map<Val, uint32_t> value_to_node_;
+};
+
+template <typename Val>
+bool MoveToFront<Val>::Insert(const Val& value) {
+ auto it = value_to_node_.find(value);
+ if (it != value_to_node_.end() && IsInTree(it->second))
+ return false;
+
+ const size_t old_size = GetSize();
+ (void)old_size;
+
+ InsertNode(CreateNode(next_timestamp_++, value));
+
+ assert(value_to_node_.count(value));
+ assert(old_size + 1 == GetSize());
+ return true;
+}
+
+template <typename Val>
+bool MoveToFront<Val>::Remove(const Val& value) {
+ auto it = value_to_node_.find(value);
+ if (it == value_to_node_.end())
+ return false;
+
+ if (!IsInTree(it->second))
+ return false;
+
+ const uint32_t orphan = RemoveNode(it->second);
+ (void)orphan;
+ // The node of |value| is still alive but it's orphaned now. Can still be
+ // reused later.
+ assert(!IsInTree(orphan));
+ assert(ValueOf(orphan) == value);
+ return true;
+}
+
+template <typename Val>
+bool MoveToFront<Val>::RankFromValue(const Val& value, size_t* rank) {
+ const size_t old_size = GetSize();
+ (void)old_size;
+ const auto it = value_to_node_.find(value);
+ if (it == value_to_node_.end()) {
+ return false;
+ }
+
+ uint32_t target = it->second;
+
+ if (!IsInTree(target)) {
+ return false;
+ }
+
+ uint32_t node = target;
+ *rank = 1 + SizeOf(LeftOf(node));
+ while (node) {
+ if (IsRightChild(node))
+ *rank += 1 + SizeOf(LeftOf(ParentOf(node)));
+ node = ParentOf(node);
+ }
+
+ // Update timestamp and reposition the node.
+ target = RemoveNode(target);
+ assert(ValueOf(target) == value);
+ assert(old_size == GetSize() + 1);
+ MutableTimestampOf(target) = next_timestamp_++;
+ InsertNode(target);
+ assert(old_size == GetSize());
+ return true;
+}
+
+template <typename Val>
+bool MoveToFront<Val>::ValueFromRank(size_t rank, Val* value) {
+ const size_t old_size = GetSize();
+ if (rank <= 0 || rank > old_size) {
+ return false;
+ }
+
+ uint32_t node = root_;
+ while (node) {
+ const size_t left_subtree_num_nodes = SizeOf(LeftOf(node));
+ if (rank == left_subtree_num_nodes + 1) {
+ // This is the node we are looking for.
+ node = RemoveNode(node);
+ assert(old_size == GetSize() + 1);
+ MutableTimestampOf(node) = next_timestamp_++;
+ InsertNode(node);
+ assert(old_size == GetSize());
+ *value = ValueOf(node);
+ return true;
+ }
+
+ if (rank < left_subtree_num_nodes + 1) {
+ // Descend into the left subtree. The rank is still valid.
+ node = LeftOf(node);
+ } else {
+ // Descend into the right subtree. We leave behind the left subtree and
+ // the current node, adjust the |rank| accordingly.
+ rank -= left_subtree_num_nodes + 1;
+ node = RightOf(node);
+ }
+ }
+
+ assert(0);
+ return false;
+}
+
+template <typename Val>
+void MoveToFront<Val>::InsertNode(uint32_t node) {
+ assert(!IsInTree(node));
+ assert(SizeOf(node) == 1);
+ assert(HeightOf(node) == 1);
+ assert(TimestampOf(node));
+
+ if (!root_) {
+ root_ = node;
+ return;
+ }
+
+ uint32_t iter = root_;
+ uint32_t parent = 0;
+
+ // Will determine if |node| will become the right or left child after
+ // insertion (but before balancing).
+ bool right_child;
+
+ // Find the node which will become |node|'s parent after insertion
+ // (but before balancing).
+ while (iter) {
+ parent = iter;
+ assert(TimestampOf(iter) != TimestampOf(node));
+ right_child = TimestampOf(iter) > TimestampOf(node);
+ iter = right_child ? RightOf(iter) : LeftOf(iter);
+ }
+
+ assert(parent);
+
+ // Connect node and parent.
+ MutableParentOf(node) = parent;
+ if (right_child)
+ MutableRightOf(parent) = node;
+ else
+ MutableLeftOf(parent) = node;
+
+ // Insertion is finished. Start the balancing process.
+ bool needs_rebalancing = true;
+ parent = ParentOf(node);
+
+ while (parent) {
+ UpdateNode(parent);
+
+ if (needs_rebalancing) {
+ const int parent_balance = BalanceOf(parent);
+
+ if (RightOf(parent) == node) {
+ // Added node to the right subtree.
+ if (parent_balance > 1) {
+ // Parent is right heavy, rotate left.
+ if (BalanceOf(node) < 0)
+ RotateRight(node);
+ parent = RotateLeft(parent);
+ } else if (parent_balance == 0 || parent_balance == -1) {
+ // Parent is balanced or left heavy, no need to balance further.
+ needs_rebalancing = false;
+ }
+ } else {
+ // Added node to the left subtree.
+ if (parent_balance < -1) {
+ // Parent is left heavy, rotate right.
+ if (BalanceOf(node) > 0)
+ RotateLeft(node);
+ parent = RotateRight(parent);
+ } else if (parent_balance == 0 || parent_balance == 1) {
+ // Parent is balanced or right heavy, no need to balance further.
+ needs_rebalancing = false;
+ }
+ }
+ }
+
+ assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1));
+
+ node = parent;
+ parent = ParentOf(parent);
+ }
+}
+
+template <typename Val>
+uint32_t MoveToFront<Val>::RemoveNode(uint32_t node) {
+ if (LeftOf(node) && RightOf(node)) {
+ // If |node| has two children, then use another node as scapegoat and swap
+ // their contents. We pick the scapegoat on the side of the tree which has more nodes.
+ const uint32_t scapegoat = SizeOf(LeftOf(node)) >= SizeOf(RightOf(node)) ?
+ RightestDescendantOf(LeftOf(node)) : LeftestDescendantOf(RightOf(node));
+ assert(scapegoat);
+ std::swap(MutableValueOf(node), MutableValueOf(scapegoat));
+ std::swap(MutableTimestampOf(node), MutableTimestampOf(scapegoat));
+ value_to_node_[ValueOf(node)] = node;
+ value_to_node_[ValueOf(scapegoat)] = scapegoat;
+ node = scapegoat;
+ }
+
+ // |node| may have only one child at this point.
+ assert(!RightOf(node) || !LeftOf(node));
+
+ uint32_t parent = ParentOf(node);
+ uint32_t child = RightOf(node) ? RightOf(node) : LeftOf(node);
+
+ // Orphan |node| and reconnect parent and child.
+ if (child)
+ MutableParentOf(child) = parent;
+
+ if (parent) {
+ if (LeftOf(parent) == node)
+ MutableLeftOf(parent) = child;
+ else
+ MutableRightOf(parent) = child;
+ }
+
+ MutableParentOf(node) = 0;
+ MutableLeftOf(node) = 0;
+ MutableRightOf(node) = 0;
+ UpdateNode(node);
+ const uint32_t orphan = node;
+
+ if (root_ == node)
+ root_ = child;
+
+ // Removal is finished. Start the balancing process.
+ bool needs_rebalancing = true;
+ node = child;
+
+ while (parent) {
+ UpdateNode(parent);
+
+ if (needs_rebalancing) {
+ const int parent_balance = BalanceOf(parent);
+
+ if (parent_balance == 1 || parent_balance == -1) {
+ // The height of the subtree was not changed.
+ needs_rebalancing = false;
+ } else {
+ if (RightOf(parent) == node) {
+ // Removed node from the right subtree.
+ if (parent_balance < -1) {
+ // Parent is left heavy, rotate right.
+ const uint32_t sibling = LeftOf(parent);
+ if (BalanceOf(sibling) > 0)
+ RotateLeft(sibling);
+ parent = RotateRight(parent);
+ }
+ } else {
+ // Removed node from the left subtree.
+ if (parent_balance > 1) {
+ // Parent is right heavy, rotate left.
+ const uint32_t sibling = RightOf(parent);
+ if (BalanceOf(sibling) < 0)
+ RotateRight(sibling);
+ parent = RotateLeft(parent);
+ }
+ }
+ }
+ }
+
+ assert(BalanceOf(parent) >= -1 && (BalanceOf(parent) <= 1));
+
+ node = parent;
+ parent = ParentOf(parent);
+ }
+
+ return orphan;
+}
+
+template <typename Val>
+uint32_t MoveToFront<Val>::RotateLeft(const uint32_t node) {
+ const uint32_t pivot = RightOf(node);
+ assert(pivot);
+
+ // LeftOf(pivot) gets attached to node in place of pivot.
+ MutableRightOf(node) = LeftOf(pivot);
+ if (RightOf(node))
+ MutableParentOf(RightOf(node)) = node;
+
+ // Pivot gets attached to ParentOf(node) in place of node.
+ MutableParentOf(pivot) = ParentOf(node);
+ if (!ParentOf(node))
+ root_ = pivot;
+ else if (IsLeftChild(node))
+ MutableLeftOf(ParentOf(node)) = pivot;
+ else
+ MutableRightOf(ParentOf(node)) = pivot;
+
+ // Node is child of pivot.
+ MutableLeftOf(pivot) = node;
+ MutableParentOf(node) = pivot;
+
+ // Update both node and pivot. Pivot is the new parent of node, so node should
+ // be updated first.
+ UpdateNode(node);
+ UpdateNode(pivot);
+
+ return pivot;
+}
+
+template <typename Val>
+uint32_t MoveToFront<Val>::RotateRight(const uint32_t node) {
+ const uint32_t pivot = LeftOf(node);
+ assert(pivot);
+
+ // RightOf(pivot) gets attached to node in place of pivot.
+ MutableLeftOf(node) = RightOf(pivot);
+ if (LeftOf(node))
+ MutableParentOf(LeftOf(node)) = node;
+
+ // Pivot gets attached to ParentOf(node) in place of node.
+ MutableParentOf(pivot) = ParentOf(node);
+ if (!ParentOf(node))
+ root_ = pivot;
+ else if (IsLeftChild(node))
+ MutableLeftOf(ParentOf(node)) = pivot;
+ else
+ MutableRightOf(ParentOf(node)) = pivot;
+
+ // Node is child of pivot.
+ MutableRightOf(pivot) = node;
+ MutableParentOf(node) = pivot;
+
+ // Update both node and pivot. Pivot is the new parent of node, so node should
+ // be updated first.
+ UpdateNode(node);
+ UpdateNode(pivot);
+
+ return pivot;
+}
+
+template <typename Val>
+void MoveToFront<Val>::UpdateNode(uint32_t node) {
+ MutableSizeOf(node) = 1 + SizeOf(LeftOf(node)) + SizeOf(RightOf(node));
+ MutableHeightOf(node) =
+ 1 + std::max(HeightOf(LeftOf(node)), HeightOf(RightOf(node)));
+}
+
+} // namespace spvutils
+
+#endif // LIBSPIRV_UTIL_MOVE_TO_FRONT_H_
--- /dev/null
+// Copyright (c) 2017 Google Inc.
+//
+// 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.
+
+#include <algorithm>
+#include <iostream>
+#include <set>
+
+#include "gmock/gmock.h"
+#include "util/move_to_front.h"
+
+namespace {
+
+using spvutils::MoveToFront;
+
+// Class used to test the inner workings of MoveToFront.
+class MoveToFrontTester : public MoveToFront<uint32_t> {
+ public:
+ // Inserts the value in the internal tree data structure. For testing only.
+ void TestInsert(uint32_t val) {
+ InsertNode(CreateNode(val, val));
+ }
+
+ // Removes the value from the internal tree data structure. For testing only.
+ void TestRemove(uint32_t val) {
+ const auto it = value_to_node_.find(val);
+ assert(it != value_to_node_.end());
+ RemoveNode(it->second);
+ }
+
+ // Prints the internal tree data structure to |out|. For testing only.
+ void PrintTree(std::ostream& out, bool print_timestamp = false) const {
+ if (root_)
+ PrintTreeInternal(out, root_, 1, print_timestamp);
+ }
+
+ // Returns node handle corresponding to the value. The value may not be in the tree.
+ uint32_t GetNodeHandle(uint32_t value) const {
+ const auto it = value_to_node_.find(value);
+ if (it == value_to_node_.end())
+ return 0;
+
+ return it->second;
+ }
+
+ // Returns total node count (both those in the tree and removed,
+ // but not the NIL singleton).
+ size_t GetTotalNodeCount() const {
+ assert(nodes_.size());
+ return nodes_.size() - 1;
+ }
+
+ private:
+ // Prints the internal tree data structure for debug purposes in the following
+ // format:
+ // 10H3S4----5H1S1-----D2
+ // 15H2S2----12H1S1----D3
+ // Right links are horizontal, left links step down one line.
+ // 5H1S1 is read as value 5, height 1, size 1. Optionally node label can also
+ // contain timestamp (5H1S1T15). D3 stands for depth 3.
+ void PrintTreeInternal(std::ostream& out, uint32_t node, size_t depth,
+ bool print_timestamp) const;
+};
+
+void MoveToFrontTester::PrintTreeInternal(
+ std::ostream& out, uint32_t node,
+ size_t depth, bool print_timestamp) const {
+ if (!node) {
+ out << "D" << depth - 1 << std::endl;
+ return;
+ }
+
+ const size_t kTextFieldWvaluethWithoutTimestamp = 10;
+ const size_t kTextFieldWvaluethWithTimestamp = 14;
+ const size_t text_field_wvalueth = print_timestamp ?
+ kTextFieldWvaluethWithTimestamp : kTextFieldWvaluethWithoutTimestamp;
+
+ std::stringstream label;
+ label << ValueOf(node) << "H" << HeightOf(node) << "S" << SizeOf(node);
+ if (print_timestamp)
+ label << "T" << TimestampOf(node);
+ const size_t label_length = label.str().length();
+ if (label_length < text_field_wvalueth)
+ label << std::string(text_field_wvalueth - label_length, '-');
+
+ out << label.str();
+
+ PrintTreeInternal(out, RightOf(node), depth + 1, print_timestamp);
+
+ if (LeftOf(node)) {
+ out << std::string(depth * text_field_wvalueth, ' ');
+ PrintTreeInternal(out, LeftOf(node), depth + 1, print_timestamp);
+ }
+}
+
+void CheckTree(const MoveToFrontTester& mtf, const std::string& expected,
+ bool print_timestamp = false) {
+ std::stringstream ss;
+ mtf.PrintTree(ss, print_timestamp);
+ EXPECT_EQ(expected, ss.str());
+}
+
+TEST(MoveToFront, EmptyTree) {
+ MoveToFrontTester mtf;
+ CheckTree(mtf, std::string());
+}
+
+TEST(MoveToFront, InsertLeftRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(30);
+ mtf.TestInsert(20);
+
+ CheckTree(mtf, std::string(R"(
+30H2S2----20H1S1----D2
+)").substr(1));
+
+ mtf.TestInsert(10);
+ CheckTree(mtf, std::string(R"(
+20H2S3----10H1S1----D2
+ 30H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, InsertRightRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(20);
+
+ CheckTree(mtf, std::string(R"(
+10H2S2----D1
+ 20H1S1----D2
+)").substr(1));
+
+ mtf.TestInsert(30);
+ CheckTree(mtf, std::string(R"(
+20H2S3----10H1S1----D2
+ 30H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, InsertRightLeftRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(30);
+ mtf.TestInsert(20);
+
+ CheckTree(mtf, std::string(R"(
+30H2S2----20H1S1----D2
+)").substr(1));
+
+ mtf.TestInsert(25);
+ CheckTree(mtf, std::string(R"(
+25H2S3----20H1S1----D2
+ 30H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, InsertLeftRightRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(20);
+
+ CheckTree(mtf, std::string(R"(
+10H2S2----D1
+ 20H1S1----D2
+)").substr(1));
+
+ mtf.TestInsert(15);
+ CheckTree(mtf, std::string(R"(
+15H2S3----10H1S1----D2
+ 20H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, RemoveSingleton) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ CheckTree(mtf, std::string(R"(
+10H1S1----D1
+)").substr(1));
+
+ mtf.TestRemove(10);
+ CheckTree(mtf, "");
+}
+
+TEST(MoveToFront, RemoveRootWithScapegoat) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(5);
+ mtf.TestInsert(15);
+ CheckTree(mtf, std::string(R"(
+10H2S3----5H1S1-----D2
+ 15H1S1----D2
+)").substr(1));
+
+ mtf.TestRemove(10);
+ CheckTree(mtf, std::string(R"(
+15H2S2----5H1S1-----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, RemoveRightRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(5);
+ mtf.TestInsert(15);
+ mtf.TestInsert(20);
+ CheckTree(mtf, std::string(R"(
+10H3S4----5H1S1-----D2
+ 15H2S2----D2
+ 20H1S1----D3
+)").substr(1));
+
+ mtf.TestRemove(5);
+
+ CheckTree(mtf, std::string(R"(
+15H2S3----10H1S1----D2
+ 20H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, RemoveLeftRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(15);
+ mtf.TestInsert(5);
+ mtf.TestInsert(1);
+ CheckTree(mtf, std::string(R"(
+10H3S4----5H2S2-----1H1S1-----D3
+ 15H1S1----D2
+)").substr(1));
+
+ mtf.TestRemove(15);
+
+ CheckTree(mtf, std::string(R"(
+5H2S3-----1H1S1-----D2
+ 10H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, RemoveLeftRightRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(15);
+ mtf.TestInsert(5);
+ mtf.TestInsert(12);
+ CheckTree(mtf, std::string(R"(
+10H3S4----5H1S1-----D2
+ 15H2S2----12H1S1----D3
+)").substr(1));
+
+ mtf.TestRemove(5);
+
+ CheckTree(mtf, std::string(R"(
+12H2S3----10H1S1----D2
+ 15H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, RemoveRightLeftRotation) {
+ MoveToFrontTester mtf;
+
+ mtf.TestInsert(10);
+ mtf.TestInsert(15);
+ mtf.TestInsert(5);
+ mtf.TestInsert(8);
+ CheckTree(mtf, std::string(R"(
+10H3S4----5H2S2-----D2
+ 8H1S1-----D3
+ 15H1S1----D2
+)").substr(1));
+
+ mtf.TestRemove(15);
+
+ CheckTree(mtf, std::string(R"(
+8H2S3-----5H1S1-----D2
+ 10H1S1----D2
+)").substr(1));
+}
+
+TEST(MoveToFront, MultipleOperations) {
+ MoveToFrontTester mtf;
+ std::vector<uint32_t> vals =
+ { 5, 11, 12, 16, 15, 6, 14, 2, 7, 10, 4, 8, 9, 3, 1, 13 };
+
+ for (uint32_t i : vals) {
+ mtf.TestInsert(i);
+ }
+
+ CheckTree(mtf, std::string(R"(
+11H5S16---5H4S10----3H3S4-----2H2S2-----1H1S1-----D5
+ 4H1S1-----D4
+ 7H3S5-----6H1S1-----D4
+ 9H2S3-----8H1S1-----D5
+ 10H1S1----D5
+ 15H3S5----13H2S3----12H1S1----D4
+ 14H1S1----D4
+ 16H1S1----D3
+)").substr(1));
+
+ mtf.TestRemove(11);
+
+ CheckTree(mtf, std::string(R"(
+10H5S15---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5
+ 4H1S1-----D4
+ 7H3S4-----6H1S1-----D4
+ 9H2S2-----8H1S1-----D5
+ 15H3S5----13H2S3----12H1S1----D4
+ 14H1S1----D4
+ 16H1S1----D3
+)").substr(1));
+
+ mtf.TestInsert(11);
+
+ CheckTree(mtf, std::string(R"(
+10H5S16---5H4S9-----3H3S4-----2H2S2-----1H1S1-----D5
+ 4H1S1-----D4
+ 7H3S4-----6H1S1-----D4
+ 9H2S2-----8H1S1-----D5
+ 13H3S6----12H2S2----11H1S1----D4
+ 15H2S3----14H1S1----D4
+ 16H1S1----D4
+)").substr(1));
+
+ mtf.TestRemove(5);
+
+ CheckTree(mtf, std::string(R"(
+10H5S15---6H4S8-----3H3S4-----2H2S2-----1H1S1-----D5
+ 4H1S1-----D4
+ 8H2S3-----7H1S1-----D4
+ 9H1S1-----D4
+ 13H3S6----12H2S2----11H1S1----D4
+ 15H2S3----14H1S1----D4
+ 16H1S1----D4
+)").substr(1));
+
+ mtf.TestInsert(5);
+
+ CheckTree(mtf, std::string(R"(
+10H5S16---6H4S9-----3H3S5-----2H2S2-----1H1S1-----D5
+ 4H2S2-----D4
+ 5H1S1-----D5
+ 8H2S3-----7H1S1-----D4
+ 9H1S1-----D4
+ 13H3S6----12H2S2----11H1S1----D4
+ 15H2S3----14H1S1----D4
+ 16H1S1----D4
+)").substr(1));
+
+ mtf.TestRemove(2);
+ mtf.TestRemove(1);
+ mtf.TestRemove(4);
+ mtf.TestRemove(3);
+ mtf.TestRemove(6);
+ mtf.TestRemove(5);
+ mtf.TestRemove(7);
+ mtf.TestRemove(9);
+
+ CheckTree(mtf, std::string(R"(
+13H4S8----10H3S4----8H1S1-----D3
+ 12H2S2----11H1S1----D4
+ 15H2S3----14H1S1----D3
+ 16H1S1----D3
+)").substr(1));
+}
+
+TEST(MoveToFront, BiggerScaleTreeTest) {
+ MoveToFrontTester mtf;
+ std::set<uint32_t> all_vals;
+
+ const uint32_t kMagic1 = 2654435761;
+ const uint32_t kMagic2 = 10000;
+
+ for (uint32_t i = 1; i < 1000; ++i) {
+ const uint32_t val = (i * kMagic1) % kMagic2;
+ if (!all_vals.count(val)) {
+ mtf.TestInsert(val);
+ all_vals.insert(val);
+ }
+ }
+
+ for (uint32_t i = 1; i < 1000; ++i) {
+ const uint32_t val = (i * kMagic1) % kMagic2;
+ if (val % 2 == 0) {
+ mtf.TestRemove(val);
+ all_vals.erase(val);
+ }
+ }
+
+ for (uint32_t i = 1000; i < 2000; ++i) {
+ const uint32_t val = (i * kMagic1) % kMagic2;
+ if (!all_vals.count(val)) {
+ mtf.TestInsert(val);
+ all_vals.insert(val);
+ }
+ }
+
+ for (uint32_t i = 1; i < 2000; ++i) {
+ const uint32_t val = (i * kMagic1) % kMagic2;
+ if (val > 50) {
+ mtf.TestRemove(val);
+ all_vals.erase(val);
+ }
+ }
+
+ EXPECT_EQ(all_vals, std::set<uint32_t>({2, 4, 11, 13, 24, 33, 35, 37, 46}));
+
+ CheckTree(mtf, std::string(R"(
+33H4S9----11H3S5----2H2S2-----D3
+ 4H1S1-----D4
+ 13H2S2----D3
+ 24H1S1----D4
+ 37H2S3----35H1S1----D3
+ 46H1S1----D3
+)").substr(1));
+}
+
+TEST(MoveToFront, RankFromValue) {
+ MoveToFrontTester mtf;
+
+ size_t rank = 0;
+ EXPECT_FALSE(mtf.RankFromValue(1, &rank));
+
+ EXPECT_TRUE(mtf.Insert(1));
+ EXPECT_TRUE(mtf.Insert(2));
+ EXPECT_TRUE(mtf.Insert(3));
+ EXPECT_FALSE(mtf.Insert(2));
+ CheckTree(mtf, std::string(R"(
+2H2S3T2-------1H1S1T1-------D2
+ 3H1S1T3-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_FALSE(mtf.RankFromValue(4, &rank));
+
+ EXPECT_TRUE(mtf.RankFromValue(1, &rank));
+ EXPECT_EQ(3u, rank);
+
+ CheckTree(mtf, std::string(R"(
+3H2S3T3-------2H1S1T2-------D2
+ 1H1S1T4-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.RankFromValue(1, &rank));
+ EXPECT_EQ(1u, rank);
+
+ EXPECT_TRUE(mtf.RankFromValue(3, &rank));
+ EXPECT_EQ(2u, rank);
+
+ EXPECT_TRUE(mtf.RankFromValue(2, &rank));
+ EXPECT_EQ(3u, rank);
+
+ EXPECT_TRUE(mtf.Insert(40));
+
+ EXPECT_TRUE(mtf.RankFromValue(1, &rank));
+ EXPECT_EQ(4u, rank);
+
+ EXPECT_TRUE(mtf.Insert(50));
+
+ EXPECT_TRUE(mtf.RankFromValue(1, &rank));
+ EXPECT_EQ(2u, rank);
+
+ CheckTree(mtf, std::string(R"(
+2H3S5T7-------3H1S1T6-------D2
+ 50H2S3T10-----40H1S1T8------D3
+ 1H1S1T11------D3
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.RankFromValue(50, &rank));
+ EXPECT_EQ(2u, rank);
+
+ EXPECT_EQ(5u, mtf.GetSize());
+ CheckTree(mtf, std::string(R"(
+2H3S5T7-------3H1S1T6-------D2
+ 1H2S3T11------40H1S1T8------D3
+ 50H1S1T12-----D3
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_FALSE(mtf.RankFromValue(0, &rank));
+ EXPECT_FALSE(mtf.RankFromValue(20, &rank));
+}
+
+TEST(MoveToFront, ValueFromRank) {
+ MoveToFrontTester mtf;
+
+ uint32_t value = 0;
+ EXPECT_FALSE(mtf.ValueFromRank(0, &value));
+ EXPECT_FALSE(mtf.ValueFromRank(1, &value));
+
+ EXPECT_TRUE(mtf.Insert(1));
+ EXPECT_TRUE(mtf.Insert(2));
+ EXPECT_TRUE(mtf.Insert(3));
+
+ EXPECT_TRUE(mtf.ValueFromRank(3, &value));
+ EXPECT_EQ(1u, value);
+
+ EXPECT_TRUE(mtf.ValueFromRank(1, &value));
+ EXPECT_EQ(1u, value);
+
+ EXPECT_TRUE(mtf.ValueFromRank(2, &value));
+ EXPECT_EQ(3u, value);
+
+ EXPECT_EQ(3u, mtf.GetSize());
+
+ CheckTree(mtf, std::string(R"(
+1H2S3T5-------2H1S1T2-------D2
+ 3H1S1T6-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.ValueFromRank(3, &value));
+ EXPECT_EQ(2u, value);
+
+ CheckTree(mtf, std::string(R"(
+3H2S3T6-------1H1S1T5-------D2
+ 2H1S1T7-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.Insert(10));
+ CheckTree(mtf, std::string(R"(
+3H3S4T6-------1H1S1T5-------D2
+ 2H2S2T7-------D2
+ 10H1S1T8------D3
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.ValueFromRank(1, &value));
+ EXPECT_EQ(10u, value);
+}
+
+TEST(MoveToFront, Remove) {
+ MoveToFrontTester mtf;
+
+ EXPECT_FALSE(mtf.Remove(1));
+ EXPECT_EQ(0u, mtf.GetTotalNodeCount());
+
+ EXPECT_TRUE(mtf.Insert(1));
+ EXPECT_TRUE(mtf.Insert(2));
+ EXPECT_TRUE(mtf.Insert(3));
+
+ CheckTree(mtf, std::string(R"(
+2H2S3T2-------1H1S1T1-------D2
+ 3H1S1T3-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_EQ(1u, mtf.GetNodeHandle(1));
+ EXPECT_EQ(3u, mtf.GetTotalNodeCount());
+ EXPECT_TRUE(mtf.Remove(1));
+ EXPECT_EQ(3u, mtf.GetTotalNodeCount());
+
+ CheckTree(mtf, std::string(R"(
+2H2S2T2-------D1
+ 3H1S1T3-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ uint32_t value = 0;
+ EXPECT_TRUE(mtf.ValueFromRank(2, &value));
+ EXPECT_EQ(2u, value);
+
+ CheckTree(mtf, std::string(R"(
+3H2S2T3-------D1
+ 2H1S1T4-------D2
+)").substr(1), /* print_timestamp = */ true);
+
+ EXPECT_TRUE(mtf.Insert(1));
+ EXPECT_EQ(1u, mtf.GetNodeHandle(1));
+ EXPECT_EQ(3u, mtf.GetTotalNodeCount());
+}
+
+TEST(MoveToFront, LargerScale) {
+ MoveToFrontTester mtf;
+ uint32_t value = 0;
+ size_t rank = 0;
+
+ for (uint32_t i = 1; i < 1000; ++i) {
+ ASSERT_TRUE(mtf.Insert(i));
+ ASSERT_EQ(i, mtf.GetSize());
+
+ ASSERT_TRUE(mtf.RankFromValue(i, &rank));
+ ASSERT_EQ(1u, rank);
+
+ ASSERT_TRUE(mtf.ValueFromRank(1, &value));
+ ASSERT_EQ(i, value);
+ }
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(1u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(2u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(3u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(4u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(5u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(999, &value));
+ ASSERT_EQ(6u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(101, &value));
+ ASSERT_EQ(905u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(101, &value));
+ ASSERT_EQ(906u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(101, &value));
+ ASSERT_EQ(907u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(201, &value));
+ ASSERT_EQ(805u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(201, &value));
+ ASSERT_EQ(806u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(201, &value));
+ ASSERT_EQ(807u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(301, &value));
+ ASSERT_EQ(705u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(301, &value));
+ ASSERT_EQ(706u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(301, &value));
+ ASSERT_EQ(707u, value);
+
+ ASSERT_TRUE(mtf.RankFromValue(605, &rank));
+ ASSERT_EQ(401u, rank);
+
+ ASSERT_TRUE(mtf.RankFromValue(606, &rank));
+ ASSERT_EQ(401u, rank);
+
+ ASSERT_TRUE(mtf.RankFromValue(607, &rank));
+ ASSERT_EQ(401u, rank);
+
+ ASSERT_TRUE(mtf.ValueFromRank(1, &value));
+ ASSERT_EQ(607u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(2, &value));
+ ASSERT_EQ(606u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(3, &value));
+ ASSERT_EQ(605u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(4, &value));
+ ASSERT_EQ(707u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(5, &value));
+ ASSERT_EQ(706u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(6, &value));
+ ASSERT_EQ(705u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(7, &value));
+ ASSERT_EQ(807u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(8, &value));
+ ASSERT_EQ(806u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(9, &value));
+ ASSERT_EQ(805u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(10, &value));
+ ASSERT_EQ(907u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(11, &value));
+ ASSERT_EQ(906u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(12, &value));
+ ASSERT_EQ(905u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(13, &value));
+ ASSERT_EQ(6u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(14, &value));
+ ASSERT_EQ(5u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(15, &value));
+ ASSERT_EQ(4u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(16, &value));
+ ASSERT_EQ(3u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(17, &value));
+ ASSERT_EQ(2u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(18, &value));
+ ASSERT_EQ(1u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(19, &value));
+ ASSERT_EQ(999u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(20, &value));
+ ASSERT_EQ(998u, value);
+
+ ASSERT_TRUE(mtf.ValueFromRank(21, &value));
+ ASSERT_EQ(997u, value);
+
+ ASSERT_TRUE(mtf.RankFromValue(997, &rank));
+ ASSERT_EQ(1u, rank);
+
+ ASSERT_TRUE(mtf.RankFromValue(998, &rank));
+ ASSERT_EQ(2u, rank);
+
+ ASSERT_TRUE(mtf.RankFromValue(996, &rank));
+ ASSERT_EQ(22u, rank);
+
+ ASSERT_TRUE(mtf.Remove(995));
+
+ ASSERT_TRUE(mtf.RankFromValue(994, &rank));
+ ASSERT_EQ(23u, rank);
+
+ for (uint32_t i = 10; i < 1000; ++i) {
+ if (i != 995) {
+ ASSERT_TRUE(mtf.Remove(i));
+ } else {
+ ASSERT_FALSE(mtf.Remove(i));
+ }
+ }
+
+ CheckTree(mtf, std::string(R"(
+6H4S9T3028----8H2S3T24------7H1S1T21------D3
+ 9H1S1T27------D3
+ 2H3S5T3032----4H2S3T3030----5H1S1T3029----D4
+ 3H1S1T3031----D4
+ 1H1S1T3033----D3
+)").substr(1), /* print_timestamp = */ true);
+
+ ASSERT_TRUE(mtf.Insert(1000));
+ ASSERT_TRUE(mtf.ValueFromRank(1, &value));
+ ASSERT_EQ(1000u, value);
+}
+
+TEST(MoveToFront, String) {
+ MoveToFront<std::string> mtf;
+
+ EXPECT_TRUE(mtf.Insert("AAA"));
+ EXPECT_TRUE(mtf.Insert("BBB"));
+ EXPECT_TRUE(mtf.Insert("CCC"));
+ EXPECT_FALSE(mtf.Insert("AAA"));
+
+ std::string value;
+ EXPECT_TRUE(mtf.ValueFromRank(2, &value));
+ EXPECT_EQ("BBB", value);
+
+ EXPECT_TRUE(mtf.ValueFromRank(2, &value));
+ EXPECT_EQ("CCC", value);
+
+ size_t rank = 0;
+ EXPECT_TRUE(mtf.RankFromValue("AAA", &rank));
+ EXPECT_EQ(3u, rank);
+
+ EXPECT_FALSE(mtf.ValueFromRank(0, &value));
+ EXPECT_FALSE(mtf.RankFromValue("ABC", &rank));
+ EXPECT_FALSE(mtf.Remove("ABC"));
+
+ EXPECT_TRUE(mtf.Remove("AAA"));
+ EXPECT_FALSE(mtf.Remove("AAA"));
+ EXPECT_FALSE(mtf.RankFromValue("AAA", &rank));
+
+ EXPECT_TRUE(mtf.Insert("AAA"));
+ EXPECT_TRUE(mtf.RankFromValue("AAA", &rank));
+ EXPECT_EQ(1u, rank);
+}
+
+} // anonymous namespace