From 5fbd118b7e0bfce3cf493772bf9c029bf37009c7 Mon Sep 17 00:00:00 2001 From: Tapajyoti Bose Date: Wed, 24 Jun 2020 12:07:50 +0530 Subject: [PATCH 1/4] Added LFU Cache --- other/lfu_cache.py | 203 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 other/lfu_cache.py diff --git a/other/lfu_cache.py b/other/lfu_cache.py new file mode 100644 index 000000000000..a515d16a695d --- /dev/null +++ b/other/lfu_cache.py @@ -0,0 +1,203 @@ +from typing import Callable, Optional + + +class DoubleLinkedListNode: + ''' + Double Linked List Node built specifically for LFU Cache + ''' + + def __init__(self, key: int, val: int): + self.key = key + self.val = val + self.freq = 0 + self.next = None + self.prev = None + + +class DoubleLinkedList: + ''' + Double Linked List built specifically for LFU Cache + ''' + + def __init__(self): + self.head = DoubleLinkedListNode(None, None) + self.rear = DoubleLinkedListNode(None, None) + self.head.next, self.rear.prev = self.rear, self.head + + def add(self, node: DoubleLinkedListNode) -> None: + ''' + Adds the given node at the head of the list and shifting it to proper position + ''' + + temp = self.rear.prev + + self.rear.prev, node.next = node, self.rear + temp.next, node.prev = node, temp + node.freq += 1 + self._position_node(node) + + def _position_node(self, node): + while node.prev.key and node.prev.freq > node.freq: + node1, node2 = node, node.prev + + node1.prev, node2.next = node2.prev, node1.prev + node1.next, node2.prev = node2, node1 + + def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: + ''' + Removes and returns the given node from the list + ''' + + temp_last, temp_next = node.prev, node.next + node.prev, node.next = None, None + temp_last.next, temp_next.prev = temp_next, temp_last + + return node + + +class LFUCache: + ''' + LFU Cache to store a given capacity of data. Can be used as a stand-alone object + or as a function decorator. + + >>> cache = LFUCache(2) + + >>> cache.set(1, 1) + + >>> cache.set(2, 2) + + >>> cache.get(1) + 1 + + >>> cache.set(3, 3) + + >>> cache.get(2) # None returned + + >>> cache.set(4, 4) + + >>> cache.get(1) # None returned + + >>> cache.get(3) + 3 + + >>> cache.get(4) + 4 + + >>> cache + CacheInfo(hits=3, misses=2, capacity=2, current size=2) + + >>> @LFUCache.decorator(100) + ... def fib(num): + ... if num in (1, 2): + ... return 1 + ... return fib(num - 1) + fib(num - 2) + + >>> for i in range(1, 101): + ... res = fib(i) + + >>> fib.cache_info() + CacheInfo(hits=196, misses=100, capacity=100, current size=100) + ''' + + # class variable to map the decorator functions to their respective instance + decorator_function_to_instance_map = {} + + def __init__(self, capacity: int): + self.list = DoubleLinkedList() + self.capacity = capacity + self.num_keys = 0 + self.hits = 0 + self.miss = 0 + self.cache = {} + + def __repr__(self) -> str: + ''' + Return the details for the cache instance + [hits, misses, capacity, current_size] + ''' + + return (f'CacheInfo(hits={self.hits}, misses={self.miss}, ' + f'capacity={self.capacity}, current size={self.num_keys})') + + def __contains__(self, key: int) -> bool: + ''' + >>> cache = LFUCache(1) + + >>> 1 in cache + False + + >>> cache.set(1, 1) + + >>> 1 in cache + True + ''' + + return key in self.cache + + def get(self, key: int) -> Optional[int]: + ''' + Returns the value for the input key and updates the Double Linked List. Returns + None if key is not present in cache + ''' + + if key in self.cache: + self.hits += 1 + self.list.add(self.list.remove(self.cache[key])) + return self.cache[key].val + self.miss += 1 + return None + + def set(self, key: int, value: int) -> None: + ''' + Sets the value for the input key and updates the Double Linked List + ''' + + if key not in self.cache: + if self.num_keys >= self.capacity: + key_to_delete = self.list.head.next.key + self.list.remove(self.cache[key_to_delete]) + del self.cache[key_to_delete] + self.num_keys -= 1 + self.cache[key] = DoubleLinkedListNode(key, value) + self.list.add(self.cache[key]) + self.num_keys += 1 + + else: + node = self.list.remove(self.cache[key]) + node.val = value + self.list.add(node) + + @staticmethod + def decorator(size: int = 128): + ''' + Decorator version of LFU Cache + ''' + + def cache_decorator_inner(func: Callable): + + def cache_decorator_wrapper(*args, **kwargs): + if func not in LFUCache.decorator_function_to_instance_map: + LFUCache.decorator_function_to_instance_map[func] = LFUCache(size) + + result = LFUCache.decorator_function_to_instance_map[func].get(args[0]) + if result is None: + result = func(*args, **kwargs) + LFUCache.decorator_function_to_instance_map[func].set( + args[0], result + ) + return result + + def cache_info(): + return LFUCache.decorator_function_to_instance_map[func] + + cache_decorator_wrapper.cache_info = cache_info + + return cache_decorator_wrapper + + return cache_decorator_inner + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From a709e57b74a0ec4df034fdd26ba69ec4a4c58692 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 24 Jun 2020 09:01:34 +0200 Subject: [PATCH 2/4] Update lfu_cache.py --- other/lfu_cache.py | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index a515d16a695d..d5be223de869 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -61,31 +61,22 @@ class LFUCache: or as a function decorator. >>> cache = LFUCache(2) - >>> cache.set(1, 1) - >>> cache.set(2, 2) - >>> cache.get(1) 1 - >>> cache.set(3, 3) - - >>> cache.get(2) # None returned - + >>> cache.get(2) + None >>> cache.set(4, 4) - - >>> cache.get(1) # None returned - + >>> cache.get(1) + None >>> cache.get(3) 3 - >>> cache.get(4) 4 - >>> cache CacheInfo(hits=3, misses=2, capacity=2, current size=2) - >>> @LFUCache.decorator(100) ... def fib(num): ... if num in (1, 2): @@ -122,16 +113,12 @@ def __repr__(self) -> str: def __contains__(self, key: int) -> bool: ''' >>> cache = LFUCache(1) - >>> 1 in cache False - >>> cache.set(1, 1) - >>> 1 in cache True ''' - return key in self.cache def get(self, key: int) -> Optional[int]: From 12df72a1f83fb74b8ee99157352f48e3077cfc6f Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jun 2020 10:53:12 +0200 Subject: [PATCH 3/4] None is returned --- other/lfu_cache.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index d5be223de869..cbe8c8d4f7f3 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -66,11 +66,9 @@ class LFUCache: >>> cache.get(1) 1 >>> cache.set(3, 3) - >>> cache.get(2) - None + >>> cache.get(2) # None is returned >>> cache.set(4, 4) - >>> cache.get(1) - None + >>> cache.get(1) # None is returned >>> cache.get(3) 3 >>> cache.get(4) From db047d5cfde694cb371f2cc8f8f8c286e9e5752d Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Thu, 25 Jun 2020 11:28:11 +0200 Subject: [PATCH 4/4] Add type hints --- other/lfu_cache.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/other/lfu_cache.py b/other/lfu_cache.py index cbe8c8d4f7f3..0f128646d7a2 100644 --- a/other/lfu_cache.py +++ b/other/lfu_cache.py @@ -36,10 +36,9 @@ def add(self, node: DoubleLinkedListNode) -> None: node.freq += 1 self._position_node(node) - def _position_node(self, node): + def _position_node(self, node: DoubleLinkedListNode) -> None: while node.prev.key and node.prev.freq > node.freq: node1, node2 = node, node.prev - node1.prev, node2.next = node2.prev, node1.prev node1.next, node2.prev = node2, node1 @@ -51,7 +50,6 @@ def remove(self, node: DoubleLinkedListNode) -> DoubleLinkedListNode: temp_last, temp_next = node.prev, node.next node.prev, node.next = None, None temp_last.next, temp_next.prev = temp_next, temp_last - return node