commit 431f4b57b718a8818c55bb9053a43e5537b30aac
Author: Arthur Lu <learthurgo@gmail.com>
Date:   Wed Feb 12 22:13:24 2025 +0000

    implement basic cache interface and baseline cache (Direct Mapped, LRU, no prefetching)

diff --git a/cache.py b/cache.py
new file mode 100644
index 0000000..e0dc67d
--- /dev/null
+++ b/cache.py
@@ -0,0 +1,103 @@
+from abc import ABC, abstractmethod
+
+# implements a simple string k-v store, objects should be serialized before putting into the cache
+class Cache(ABC):
+    # constructor taking in the cache size limit as number of entries
+    @abstractmethod
+    def __init__(self, limit: int):
+        pass
+
+    # get the value corresponding to key or returns None if there was a cache miss
+    @abstractmethod
+    def get(self, key: str) -> str:
+        pass 
+    
+    # set the value corresponding to key and returns True if an eviction was made
+    @abstractmethod
+    def put(self, key: str, val: str) -> bool:
+        pass
+
+    # mark cache item as invalid and returns True if the element was found and invalidated
+    @abstractmethod
+    def invalidate(self, key: str) -> bool:
+        pass
+
+from collections import OrderedDict
+
+# the baseline cache using Direct Mapping, LRU eviction, and no prefetching
+class BaselineCache(Cache):
+
+    limit = None
+    cache = None
+
+    def __init__(self, limit: int):
+        super()
+        self.limit = limit
+        self.cache = OrderedDict()
+        
+    def __eq__(self, other):
+        return self.cache == other
+    
+    def __len__(self):
+        return len(self.cache)
+
+    def get(self, key: str) -> str:
+        if key in self.cache:
+            self.cache.move_to_end(key)
+            return self.cache[key]
+        else:
+            return None
+
+    def put(self, key: str, val: str) -> bool:
+        # LRU evict
+        evict = False
+        if len(self.cache) >= self.limit:
+            self.cache.popitem(last = False)
+            evict = True
+        
+        self.cache[key] = val
+        self.cache.move_to_end(key)
+
+        return evict
+
+    def invalidate(self, key: str) -> bool:
+        # basic delete invalidation, no (p)refetching
+        if key in self.cache:
+            del self.cache[key]
+            return True
+        else:
+            return False
+
+if __name__ == "__main__": # basic testing, should never be called when importing
+    cache = BaselineCache(10)
+
+    for i in range(10):
+        assert cache.put(str(i), str(i+1)) == False
+    
+    assert len(cache) == 10
+    assert cache == OrderedDict({'0': '1', '1': '2', '2': '3', '3': '4', '4': '5', '5': '6', '6': '7', '7': '8', '8': '9', '9': '10'})
+
+    assert cache.get("5") == "6"
+    assert cache.get("8") == "9"
+    assert cache.get("0") == "1"
+
+    assert len(cache) == 10
+    assert cache == OrderedDict({'1': '2', '2': '3', '3': '4', '4': '5', '6': '7', '7': '8', '9': '10', '5': '6', '8': '9', '0': '1'})
+
+    assert cache.get("a") == None
+    assert cache.get("b") == None
+    assert cache.get("c") == None
+
+    assert cache.put("a", "b") == True
+    assert cache.put("b", "c") == True
+    assert cache.put("c", "d") == True
+
+    assert len(cache) == 10
+    assert cache == OrderedDict({'4': '5', '6': '7', '7': '8', '9': '10', '5': '6', '8': '9', '0': '1', 'a': 'b', 'b' : 'c', 'c': 'd'})
+
+    assert cache.get("c") == "d"
+    assert cache.get("b") == "c"
+    assert cache.get("a") == "b"
+
+    assert len(cache) == 10
+    assert cache == OrderedDict({'4': '5', '6': '7', '7': '8', '9': '10', '5': '6', '8': '9', '0': '1', 'c': 'd', 'b' : 'c', 'a': 'b'})
\ No newline at end of file