/* * Copyright (C) 2021 The Android Open Source Project * * 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. */ #pragma once #include #include #include namespace android::mediametrics { /** * LruSet keeps a set of the last "Size" elements added or accessed. * * (Lru stands for least-recently-used eviction policy). * * Runs in O(1) time for add, remove, and check. Internally implemented * with an unordered_map and a list. In order to remove elements, * a list iterator is stored in the unordered_map * (noting that std::list::erase() contractually * does not affect iterators other than the one erased). */ template class LruSet { const size_t mMaxSize; std::list mAccessOrder; // front is the most recent, back is the oldest. // item T with its access order iterator. std::unordered_map::iterator> mMap; public: /** * Constructs a LruSet which checks whether the element was * accessed or added recently. * * The parameter maxSize is used to cap growth of LruSet; * eviction is based on least recently used LRU. * If maxSize is zero, the LruSet contains no elements * and check() always returns false. * * \param maxSize the maximum number of elements that are tracked. */ explicit LruSet(size_t maxSize) : mMaxSize(maxSize) {} /** * Returns the number of entries in the LruSet. * * This is a number between 0 and maxSize. */ size_t size() const { return mMap.size(); } /** Clears the container contents. */ void clear() { mMap.clear(); mAccessOrder.clear(); } /** Returns a string dump of the last n entries. */ std::string dump(size_t n) const { std::stringstream ss; auto it = mAccessOrder.cbegin(); for (size_t i = 0; i < n && it != mAccessOrder.cend(); ++i) { ss << *it++ << "\n"; } return ss.str(); } /** Adds a new item to the set. */ void add(const T& t) { if (mMaxSize == 0) return; auto it = mMap.find(t); if (it != mMap.end()) { // already exists. mAccessOrder.erase(it->second); // move-to-front on the chronologically ordered list. } else if (mAccessOrder.size() >= mMaxSize) { const T last = mAccessOrder.back(); mAccessOrder.pop_back(); mMap.erase(last); } mAccessOrder.push_front(t); mMap[t] = mAccessOrder.begin(); } /** * Removes an item from the set. * * \param t item to be removed. * \return false if the item doesn't exist. */ bool remove(const T& t) { auto it = mMap.find(t); if (it == mMap.end()) return false; mAccessOrder.erase(it->second); mMap.erase(it); return true; } /** Returns true if t is present (and moves the access order of t to the front). */ bool check(const T& t) { // not const, as it adjusts the least-recently-used order. auto it = mMap.find(t); if (it == mMap.end()) return false; mAccessOrder.erase(it->second); mAccessOrder.push_front(it->first); it->second = mAccessOrder.begin(); return true; } }; } // namespace android::mediametrics