diff --git a/README.md b/README.md index 8d872e9..59c4f2f 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/least_frequently_used.hpp b/least_frequently_used.hpp new file mode 100644 index 0000000..90c601a --- /dev/null +++ b/least_frequently_used.hpp @@ -0,0 +1,102 @@ +// Andrew Naplavkov + +#ifndef STEP20_LEAST_FREQUENTLY_USED_HPP +#define STEP20_LEAST_FREQUENTLY_USED_HPP + +#include +#include + +namespace step20::least_frequently_used { + +/// An O(1) algorithm for implementing the LFU cache eviction scheme +template , + class KeyEqual = std::equal_to> +class cache { + struct item_type; + using item_list = std::list; + using item_iterator = typename item_list::iterator; + struct freq_type; + using freq_list = std::list; + 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 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(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 diff --git a/least_recently_used.hpp b/least_recently_used.hpp new file mode 100644 index 0000000..c51b807 --- /dev/null +++ b/least_recently_used.hpp @@ -0,0 +1,101 @@ +// Andrew Naplavkov + +#ifndef STEP20_LEAST_RECENTLY_USED_HPP +#define STEP20_LEAST_RECENTLY_USED_HPP + +#include +#include + +namespace step20::least_recently_used { +namespace detail { + +template , + class KeyEqual = std::equal_to> +class linked_hash_map { +public: + using value_type = std::pair; + using list_type = std::list; + 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 + std::pair 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(val)); + try { + map_.emplace(key, it); + return {it, true}; + } + catch (...) { + list_.erase(it); + throw; + } + } + +private: + list_type list_; + std::unordered_map map_; +}; + +} // namespace detail + +/// An O(1) algorithm for implementing the LRU cache eviction scheme +template , + class KeyEqual = std::equal_to> +class cache { + detail::linked_hash_map 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 diff --git a/suffix_tree.hpp b/suffix_tree.hpp index 28a0aac..7777234 100644 --- a/suffix_tree.hpp +++ b/suffix_tree.hpp @@ -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); diff --git a/test/main.cpp b/test/main.cpp index bcbefa3..ba71409 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -275,6 +277,37 @@ _1->0 [label="bxa$"] } } +void test_least_frequently_used_hello_world() +{ + log("run"); + auto lfu = least_frequently_used::cache(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(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"); @@ -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();