Skip to content

Commit 3868951

Browse files
first commit
0 parents  commit 3868951

6 files changed

+278
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.pyc

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#LRU Cache
2+
3+
**Description**:
4+
LRU Cache implementation with min heap instead of ordereddict.
5+
This was mainly an exercise in implementing a min heap, which is often used in priority queues.
6+
7+
**Run tests**:
8+
To run the tests, you need to have nose installed. If you're on a UNIX-like system, you can run `easy_install nose`, `pip install nose` (you'll probably need to run these commands as root or using sudo).
9+
10+
Once nose is installed, you can simply run `nosetests` in the root directory of the program.
11+
12+

lru_cache.py

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#LRU CACHE USING MIN HEAP INSTEAD OF ORDEREDDICT
2+
3+
import time
4+
5+
6+
class MinHeap(object):
7+
def __init__(self, l=[]):
8+
self._heap = []
9+
if len(l) > 0:
10+
for e in l:
11+
self.add(e[0], e[1])
12+
13+
def _parent(self, i):
14+
return (i-1)/2
15+
16+
def _left_child(self, i):
17+
return 2*i + 1
18+
19+
def _right_child(self, i):
20+
return 2*i + 2
21+
22+
def _is_leaf(self, i):
23+
return self._left_child(i) >= len(self._heap) and self._right_child(i) >= len(self._heap)
24+
25+
def _one_child(self, i):
26+
return self._left_child(i) < len(self._heap) and self._right_child(i) >= len(self._heap)
27+
28+
def _down_heapify(self, i):
29+
if self._is_leaf(i):
30+
return self._heap
31+
32+
if self._one_child(i):
33+
if self._heap[self._left_child(i)][1] < self._heap[i][1]:
34+
self._heap[self._left_child(i)], self._heap[i] = self._heap[i], self._heap[self._left_child(i)]
35+
return self._heap
36+
37+
if min(self._heap[self._left_child(i)][1], self._heap[self._right_child(i)][1]) >= self._heap[i][1]:
38+
return self._heap
39+
40+
if self._heap[self._left_child(i)][1] < self._heap[self._right_child(i)][1]:
41+
self._heap[self._left_child(i)], self._heap[i] = self._heap[i], self._heap[self._left_child(i)]
42+
self._down_heapify(self._left_child(i))
43+
return self._heap
44+
45+
self._heap[self._right_child(i)], self._heap[i] = self._heap[i], self._heap[self._right_child(i)]
46+
self._down_heapify(self._right_child(i))
47+
return self._heap
48+
49+
def _up_heapify(self, i):
50+
if self._parent(i) >= 0:
51+
if self._heap[self._parent(i)][1] > self._heap[i][1]:
52+
self._heap[self._parent(i)], self._heap[i] = self._heap[i], self._heap[self._parent(i)]
53+
self._up_heapify(self._parent(i))
54+
return self._heap
55+
return self._heap
56+
57+
def validate_heap(self):
58+
for i, e in enumerate(self._heap):
59+
if self._parent(i) >= 0:
60+
if self._heap[self._parent(i)][1] > self._heap[i][1]:
61+
print self._heap[self._parent(i)], self._heap[i]
62+
return False
63+
return True
64+
65+
def add(self, key, value):
66+
self._heap.append((key, value))
67+
self._up_heapify(len(self._heap) - 1)
68+
69+
def pop(self):
70+
min_value = self._heap[0]
71+
self._heap[0] = self._heap.pop()
72+
self._down_heapify(0)
73+
return min_value
74+
75+
def update(self, key, value):
76+
for i, e in enumerate(self._heap):
77+
if e[0] == key:
78+
self._heap[i] = (key, value)
79+
if value > e[1]:
80+
self._down_heapify(i)
81+
else:
82+
self._up_heapify(i)
83+
return self._heap
84+
return self._heap
85+
86+
def delete(self, key):
87+
for i, e in enumerate(self._heap):
88+
if e[0] == key:
89+
last_element = self._heap.pop()
90+
self._heap[i] = last_element
91+
self._down_heapify(i)
92+
return self._heap
93+
return self._heap
94+
95+
96+
class LRUCache(object):
97+
def __init__(self, capacity):
98+
self._cache_dict = {}
99+
self._cache_heap = MinHeap()
100+
self._capacity = capacity
101+
102+
def _is_in_cache(self, key):
103+
if self._cache_dict.get(key):
104+
return True
105+
return False
106+
107+
def _is_over_capacity(self):
108+
if len(self._cache_dict) >= self._capacity:
109+
return True
110+
return False
111+
112+
def set(self, key, value):
113+
new_time = time.time()
114+
115+
if self._is_in_cache(key):
116+
self._cache_heap.update(key, new_time)
117+
self._cache_dict[key] = (value, new_time)
118+
return self._cache_dict
119+
120+
if self._is_over_capacity():
121+
min_value = self._cache_heap.pop()[0]
122+
del self._cache_dict[min_value]
123+
124+
self._cache_heap.add(key, new_time)
125+
self._cache_dict[key] = (value, new_time)
126+
return self._cache_dict
127+
128+
def get(self, key):
129+
if not self._is_in_cache(key):
130+
raise KeyError("this key is not in the cache")
131+
132+
new_time = time.time()
133+
self._cache_heap.update(key, new_time)
134+
value = self._cache_dict[key][0]
135+
self._cache_dict[key] = (value, new_time)
136+
return value
137+
138+
def get_lru_el(self):
139+
key = self._cache_heap._heap[0][0]
140+
return self._cache_dict[key]
141+
142+
def get_dict(self):
143+
return self._cache_dict
144+
145+
146+
lru_cache = LRUCache(3)
147+
148+
lru_cache.set("user1", "teme")
149+
lru_cache.set("user2", "oge")
150+
lru_cache.set("user3", "dzenan")
151+
lru_cache.set("user4", "francis")
152+
lru_cache.set("user5", "baba")
153+
154+
lru_cache.get("user3")
155+
lru_cache.get("user4")
156+
157+
lru_cache.set("user2", "oge")
158+
lru_cache.set("user6", "tommy")
159+
160+
print lru_cache.get_dict()
161+
print lru_cache.get_lru_el()
162+
163+
164+
165+
166+

tests/__init__.py

Whitespace-only changes.

tests/lru_cache_scale_tests.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import unittest
2+
from nose.tools import raises
3+
4+
from lru_cache import LRUCache
5+
6+
class LRUCacheScaleTests(unittest.TestCase):
7+
def setUp(self):
8+
self.capacity = 500000
9+
self.lru_cache = LRUCache(self.capacity)
10+
11+
for i in xrange(1, self.capacity + 2):
12+
self.lru_cache.set('user' + str(i), 'user_number_' + str(i))
13+
14+
def tearDown(self):
15+
self.lru_cache = None
16+
17+
@raises(KeyError)
18+
def test_set_and_get(self):
19+
"""
20+
lru element should be user2 and user1 sould be removed.
21+
"""
22+
23+
self.assertEqual(self.lru_cache.get_lru_el(), self.lru_cache._cache_dict['user2'])
24+
self.lru_cache.get('user1')
25+
26+
def test_update(self):
27+
"""
28+
test update
29+
"""
30+
31+
self.lru_cache.set('user' + str(self.capacity/2), 'ANON_USER')
32+
self.assertTrue(self.lru_cache.get('user' + str(self.capacity/2)) == 'ANON_USER')
33+
34+
self.lru_cache.set('user2', 'USER2')
35+
self.assertEqual(self.lru_cache.get_lru_el(), self.lru_cache._cache_dict['user3'])
36+
37+

tests/lru_cache_tests.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import unittest
2+
from nose.tools import raises
3+
4+
from lru_cache import LRUCache
5+
6+
class LRUCacheTests(unittest.TestCase):
7+
def setUp(self):
8+
self.lru_cache = LRUCache(3)
9+
self.lru_cache.set('user1', 'timur')
10+
self.lru_cache.set('user2', 'ogden')
11+
self.lru_cache.set('user3', 'francis')
12+
self.lru_cache.set('user4', 'amra')
13+
14+
def tearDown(self):
15+
self.lru_cache = None
16+
17+
@raises(KeyError)
18+
def test_key_error(self):
19+
"""
20+
First element should be removed from cache
21+
"""
22+
self.lru_cache.get('user1')
23+
24+
25+
def test_get(self):
26+
"""
27+
Checks to see if 3 most recent elements are in the cache
28+
"""
29+
self.assertTrue(self.lru_cache.get('user2') == 'ogden')
30+
self.assertTrue(self.lru_cache.get('user3') == 'francis')
31+
self.assertTrue(self.lru_cache.get('user4') == 'amra')
32+
33+
@raises(KeyError)
34+
def test_lru_get_and_set(self):
35+
"""
36+
Performs a few get and set operations: user3 should be the least recently used element and should raise a key error.
37+
user2, user 4 and user5 should still be in the cache
38+
"""
39+
self.lru_cache.get('user2')
40+
self.lru_cache.set('user5', 'tom')
41+
42+
self.assertTrue(self.lru_cache.get('user2') == 'ogden')
43+
self.assertTrue(self.lru_cache.get('user4') == 'amra')
44+
self.assertTrue(self.lru_cache.get('user5') == 'tom')
45+
46+
self.lru_cache.get('user3')
47+
48+
@raises(KeyError)
49+
def test_update(self):
50+
"""
51+
Updates user2 before adding new element. user4 should be the lru element and user3 should be removed from cache.
52+
"""
53+
self.lru_cache.set('user2', 'Ogden')
54+
self.lru_cache.set('user5', 'tom')
55+
56+
self.assertTrue(self.lru_cache.get('user2') == 'Ogden')
57+
self.assertFalse(self.lru_cache.get('user2') == 'ogden')
58+
59+
self.assertTrue(self.lru_cache.get_lru_el() == self.lru_cache._cache_dict['user4'])
60+
self.lru_cache.get('user3')
61+
62+

0 commit comments

Comments
 (0)