Skip to content

Commit

Permalink
LFU, LRU
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Naplavkov committed May 20, 2023
1 parent e96a600 commit e9d6fcb
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 7 deletions.
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ Step20 is a C++20, header-only library of STL-like algorithms and data structure
### Table of Contents (wiki, examples)

* [edit distance](https://en.wikipedia.org/wiki/Levenshtein_distance):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L139-L146)
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L141-L148)
* [LFU cache](https://en.wikipedia.org/wiki/Least_frequently_used):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L283-L293)
* [LRU cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L299-L308)
* [longest common subsequence](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L304-L308),
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L337-L341),
[example](https://github.com/storm-ptr/step20/blob/main/example/diff/diff.hpp#L49-L67)
* [longest common substring](https://en.wikipedia.org/wiki/Longest_common_substring_problem):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L325-L329)
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L358-L362)
* [longest repeated substring](https://en.wikipedia.org/wiki/Longest_repeated_substring_problem):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L371-L373)
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L404-L406)
* [suffix array](https://en.wikipedia.org/wiki/Suffix_array):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L452-L453)
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L485-L486)
* [suffix tree](https://en.wikipedia.org/wiki/Suffix_tree):
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L481-L482),
[snippet](https://github.com/storm-ptr/step20/blob/main/test/main.cpp#L514-L515),
[example](https://github.com/storm-ptr/step20/blob/main/example/suffix_tree_viz/suffix_tree_viz.hpp#L16-L42)
102 changes: 102 additions & 0 deletions least_frequently_used.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Andrew Naplavkov

#ifndef STEP20_LEAST_FREQUENTLY_USED_HPP
#define STEP20_LEAST_FREQUENTLY_USED_HPP

#include <list>
#include <unordered_map>

namespace step20::least_frequently_used {

/// An O(1) algorithm for implementing the LFU cache eviction scheme
template <class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>>
class cache {
struct item_type;
using item_list = std::list<item_type>;
using item_iterator = typename item_list::iterator;
struct freq_type;
using freq_list = std::list<freq_type>;
using freq_iterator = typename freq_list::iterator;

struct item_type {
freq_iterator parent;
Key key;
T val;
};

struct freq_type {
size_t n;
item_list items;
};

freq_list list_;
std::unordered_map<Key, item_iterator, Hash, KeyEqual> map_;
size_t capacity_;

freq_iterator emplace(freq_iterator it, size_t n)
{
return it != list_.end() && it->n == n ? it : list_.emplace(it, n);
}

public:
explicit cache(size_t capacity) : capacity_(capacity) {}

/// @return nullptr if key is not found
const T* find(const Key& key)
{
auto it = map_.find(key);
if (it == map_.end())
return nullptr;
auto item = it->second;
auto freq = item->parent;
auto next = emplace(std::next(freq), freq->n + 1);
try {
next->items.splice(next->items.end(), freq->items, item);
}
catch (...) {
if (next->items.empty())
list_.erase(next);
throw;
}
item->parent = next;
if (freq->items.empty())
list_.erase(freq);
return std::addressof(item->val);
}

/// Basic exception guarantee.
/// Evicted item is not restored if an exception occurs.
void insert_or_assign(const Key& key, const T& val)
{
if (auto ptr = find(key)) {
*const_cast<T*>(ptr) = val;
return;
}
if (!map_.empty() && map_.size() >= capacity_) {
auto freq = list_.begin();
auto item = freq->items.begin();
map_.erase(item->key);
freq->items.erase(item);
if (freq->items.empty())
list_.erase(freq);
}
auto freq = emplace(list_.begin(), 1);
bool was_freq_empty = freq->items.empty();
try {
auto item = freq->items.emplace(freq->items.end(), freq, key, val);
map_.emplace(key, item);
}
catch (...) {
if (was_freq_empty)
list_.erase(freq);
throw;
}
}
};

} // namespace step20::least_frequently_used

#endif // STEP20_LEAST_FREQUENTLY_USED_HPP
101 changes: 101 additions & 0 deletions least_recently_used.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Andrew Naplavkov

#ifndef STEP20_LEAST_RECENTLY_USED_HPP
#define STEP20_LEAST_RECENTLY_USED_HPP

#include <list>
#include <unordered_map>

namespace step20::least_recently_used {
namespace detail {

template <class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>>
class linked_hash_map {
public:
using value_type = std::pair<const Key, T>;
using list_type = std::list<value_type>;
using iterator = typename list_type::iterator;

auto size() const { return list_.size(); }
iterator begin() { return list_.begin(); }
iterator end() { return list_.end(); }
void transfer(iterator from, iterator to) { list_.splice(to, list_, from); }

iterator find(const Key& key)
{
auto it = map_.find(key);
return it == map_.end() ? list_.end() : it->second;
}

iterator erase(iterator it)
{
map_.erase(it->first);
return list_.erase(it);
}

template <class M>
std::pair<iterator, bool> emplace(iterator it, const Key& key, M&& val)
{
if (auto pos = find(key); pos != end())
return {pos, false};
it = list_.emplace(it, key, std::forward<M>(val));
try {
map_.emplace(key, it);
return {it, true};
}
catch (...) {
list_.erase(it);
throw;
}
}

private:
list_type list_;
std::unordered_map<Key, iterator, Hash, KeyEqual> map_;
};

} // namespace detail

/// An O(1) algorithm for implementing the LRU cache eviction scheme
template <class Key,
class T,
class Hash = std::hash<Key>,
class KeyEqual = std::equal_to<Key>>
class cache {
detail::linked_hash_map<Key, T, Hash, KeyEqual> map_;
size_t capacity_;

public:
explicit cache(size_t capacity) : capacity_(capacity) {}

/// @return nullptr if key is not found
const T* find(const Key& key)
{
auto it = map_.find(key);
if (it == map_.end())
return nullptr;
map_.transfer(it, map_.end());
return std::addressof(it->second);
}

/// Basic exception guarantee.
/// Inserted item remains if an exception occurs.
void insert_or_assign(const Key& key, const T& val)
{
auto [it, success] = map_.emplace(map_.end(), key, val);
if (!success) {
map_.transfer(it, map_.end());
it->second = val;
return;
}
while (map_.size() > capacity_)
map_.erase(map_.begin());
}
};

} // namespace step20::least_recently_used

#endif // STEP20_LEAST_RECENTLY_USED_HPP
3 changes: 2 additions & 1 deletion suffix_tree.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class suffix_tree {
pos_ = node_ = 0;
}

/// Basic exception guarantee
/// Basic exception guarantee.
/// Content is released if an exception occurs.
void push_back(Char ch)
try {
str_.push_back(ch);
Expand Down
35 changes: 35 additions & 0 deletions test/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <step20/edit_distance.hpp>
#include <step20/example/diff/diff.hpp>
#include <step20/example/suffix_tree_viz/suffix_tree_viz.hpp>
#include <step20/least_frequently_used.hpp>
#include <step20/least_recently_used.hpp>
#include <step20/longest_common_subsequence.hpp>
#include <step20/longest_common_substring.hpp>
#include <step20/longest_repeated_substring.hpp>
Expand Down Expand Up @@ -275,6 +277,37 @@ _1->0 [label="bxa$"]
}
}

void test_least_frequently_used_hello_world()
{
log("run");
auto lfu = least_frequently_used::cache<int, int>(2);
lfu.insert_or_assign(1, 1);
lfu.insert_or_assign(2, 2);
check(*lfu.find(1) == 1);
lfu.insert_or_assign(3, 3);
check(lfu.find(2) == nullptr);
check(*lfu.find(3) == 3);
lfu.insert_or_assign(4, 4);
check(lfu.find(1) == nullptr);
check(*lfu.find(3) == 3);
check(*lfu.find(4) == 4);
}

void test_least_recently_used_hello_world()
{
log("run");
auto lru = least_recently_used::cache<int, int>(2);
lru.insert_or_assign(1, 1);
lru.insert_or_assign(2, 2);
check(*lru.find(1) == 1);
lru.insert_or_assign(3, 3);
check(lru.find(2) == nullptr);
lru.insert_or_assign(4, 4);
check(lru.find(1) == nullptr);
check(*lru.find(3) == 3);
check(*lru.find(4) == 4);
}

void test_longest_common_subsequence_case_insensitive()
{
log("run");
Expand Down Expand Up @@ -488,6 +521,8 @@ int main()
test_edit_distance_hello_world();
test_example_diff();
test_example_suffix_tree_viz();
test_least_frequently_used_hello_world();
test_least_recently_used_hello_world();
test_longest_common_subsequence_case_insensitive();
test_longest_common_subsequence_hello_world();
test_longest_common_substring_case_insensitive();
Expand Down

0 comments on commit e9d6fcb

Please sign in to comment.