@@ -170,32 +170,78 @@ def popitem(self):
170
170
class LFUCache (Cache ):
171
171
"""Least Frequently Used (LFU) cache implementation."""
172
172
173
+ class _Link :
174
+ __slots__ = ("count" , "keys" , "next" , "prev" )
175
+
176
+ def __init__ (self , count ):
177
+ self .count = count
178
+ self .keys = set ()
179
+
180
+ def unlink (self ):
181
+ next = self .next
182
+ prev = self .prev
183
+ prev .next = next
184
+ next .prev = prev
185
+
173
186
def __init__ (self , maxsize , getsizeof = None ):
174
187
Cache .__init__ (self , maxsize , getsizeof )
175
- self .__counter = collections .Counter ()
188
+ self .__root = root = LFUCache ._Link (0 ) # sentinel
189
+ root .prev = root .next = root
190
+ self .__links = {}
176
191
177
192
def __getitem__ (self , key , cache_getitem = Cache .__getitem__ ):
178
193
value = cache_getitem (self , key )
179
194
if key in self : # __missing__ may not store item
180
- self .__counter [ key ] -= 1
195
+ self .__touch ( key )
181
196
return value
182
197
183
198
def __setitem__ (self , key , value , cache_setitem = Cache .__setitem__ ):
184
199
cache_setitem (self , key , value )
185
- self .__counter [key ] -= 1
200
+ if key in self .__links :
201
+ return self .__touch (key )
202
+ root = self .__root
203
+ link = root .next
204
+ if link .count != 1 :
205
+ link = LFUCache ._Link (1 )
206
+ link .next = root .next
207
+ root .next = link .next .prev = link
208
+ link .prev = root
209
+ link .keys .add (key )
210
+ self .__links [key ] = link
186
211
187
212
def __delitem__ (self , key , cache_delitem = Cache .__delitem__ ):
188
213
cache_delitem (self , key )
189
- del self .__counter [key ]
214
+ link = self .__links .pop (key )
215
+ link .keys .remove (key )
216
+ if not link .keys :
217
+ link .unlink ()
190
218
191
219
def popitem (self ):
192
220
"""Remove and return the `(key, value)` pair least frequently used."""
193
- try :
194
- (( key , _ ),) = self . __counter . most_common ( 1 )
195
- except ValueError :
221
+ root = self . __root
222
+ curr = root . next
223
+ if curr is root :
196
224
raise KeyError ("%s is empty" % type (self ).__name__ ) from None
197
- else :
198
- return (key , self .pop (key ))
225
+ key = next (iter (curr .keys )) # remove an arbitrary element
226
+ return (key , self .pop (key ))
227
+
228
+ def __touch (self , key ):
229
+ """Increment use count"""
230
+ link = self .__links [key ]
231
+ curr = link .next
232
+ if curr .count != link .count + 1 :
233
+ if len (link .keys ) == 1 :
234
+ link .count += 1
235
+ return
236
+ curr = LFUCache ._Link (link .count + 1 )
237
+ curr .next = link .next
238
+ link .next = curr .next .prev = curr
239
+ curr .prev = link
240
+ curr .keys .add (key )
241
+ link .keys .remove (key )
242
+ if not link .keys :
243
+ link .unlink ()
244
+ self .__links [key ] = curr
199
245
200
246
201
247
class LRUCache (Cache ):
0 commit comments