blob: 1f0ab604fc1afe44172aaebd06864d02970629cc [file] [log] [blame]
/*
* 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 <list>
#include <sstream>
#include <unordered_map>
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 <typename T>
class LruSet {
const size_t mMaxSize;
std::list<T> mAccessOrder; // front is the most recent, back is the oldest.
// item T with its access order iterator.
std::unordered_map<T, typename std::list<T>::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