1- """
2- LFU cache Written by Shane Wang
3- https://medium.com/@epicshane/a-python-implementation-of-lfu-least-frequently-used-cache-with-o-1-time-complexity-e16b34a3c49b
4- https://github.com/luxigner/lfu_cache
5- Modified by Sep Dehpour
6- """
71from collections import defaultdict
8- from threading import Lock
9- from statistics import mean
10- from deepdiff .helper import not_found , dict_ , SetOrdered
2+ from cachebox import LRUCache
3+ from deepdiff .helper import SetOrdered , not_found
114
125
13- class CacheNode :
14- def __init__ (self , key , report_type , value , freq_node , pre , nxt ):
15- self .key = key
16- if report_type :
17- self .content = defaultdict (SetOrdered )
18- self .content [report_type ].add (value )
19- else :
20- self .content = value
21- self .freq_node = freq_node
22- self .pre = pre # previous CacheNode
23- self .nxt = nxt # next CacheNode
24-
25- def free_myself (self ):
26- if self .freq_node .cache_head == self .freq_node .cache_tail : # type: ignore
27- self .freq_node .cache_head = self .freq_node .cache_tail = None # type: ignore
28- elif self .freq_node .cache_head == self : # type: ignore
29- self .nxt .pre = None # type: ignore
30- self .freq_node .cache_head = self .nxt # type: ignore
31- elif self .freq_node .cache_tail == self : # type: ignore
32- self .pre .nxt = None # type: ignore
33- self .freq_node .cache_tail = self .pre # type: ignore
34- else :
35- self .pre .nxt = self .nxt # type: ignore
36- self .nxt .pre = self .pre # type: ignore
37-
38- self .pre = None
39- self .nxt = None
40- self .freq_node = None
41-
42-
43- class FreqNode :
44- def __init__ (self , freq , pre , nxt ):
45- self .freq = freq
46- self .pre = pre # previous FreqNode
47- self .nxt = nxt # next FreqNode
48- self .cache_head = None # CacheNode head under this FreqNode
49- self .cache_tail = None # CacheNode tail under this FreqNode
50-
51- def count_caches (self ):
52- if self .cache_head is None and self .cache_tail is None :
53- return 0
54- elif self .cache_head == self .cache_tail :
55- return 1
56- else :
57- return '2+'
58-
59- def remove (self ):
60- if self .pre is not None :
61- self .pre .nxt = self .nxt
62- if self .nxt is not None :
63- self .nxt .pre = self .pre
64-
65- pre = self .pre
66- nxt = self .nxt
67- self .pre = self .nxt = self .cache_head = self .cache_tail = None
68-
69- return (pre , nxt )
70-
71- def pop_head_cache (self ):
72- if self .cache_head is None and self .cache_tail is None :
73- return None
74- elif self .cache_head == self .cache_tail :
75- cache_head = self .cache_head
76- self .cache_head = self .cache_tail = None
77- return cache_head
78- else :
79- cache_head = self .cache_head
80- self .cache_head .nxt .pre = None # type: ignore
81- self .cache_head = self .cache_head .nxt # type: ignore
82- return cache_head
83-
84- def append_cache_to_tail (self , cache_node ):
85- cache_node .freq_node = self
86-
87- if self .cache_head is None and self .cache_tail is None :
88- self .cache_head = self .cache_tail = cache_node
89- else :
90- cache_node .pre = self .cache_tail
91- cache_node .nxt = None
92- self .cache_tail .nxt = cache_node # type: ignore
93- self .cache_tail = cache_node
94-
95- def insert_after_me (self , freq_node ):
96- freq_node .pre = self
97- freq_node .nxt = self .nxt
98-
99- if self .nxt is not None :
100- self .nxt .pre = freq_node
101-
102- self .nxt = freq_node
103-
104- def insert_before_me (self , freq_node ):
105- if self .pre is not None :
106- self .pre .nxt = freq_node
107-
108- freq_node .pre = self .pre
109- freq_node .nxt = self
110- self .pre = freq_node
6+ class DistanceCache :
7+ """
8+ Native bounded cache used by DeepDiff's distance calculations.
1119
112-
113- class LFUCache :
10+ DeepDiff historically used a pure Python LFU cache here. The distance-cache
11+ hot path benefits more from cachebox's native mapping operations than from
12+ preserving LFU eviction semantics.
13+ """
11414
11515 def __init__ (self , capacity ):
116- self .cache = dict_ () # {key: cache_node}
11716 if capacity <= 0 :
118- raise ValueError ('Capacity of LFUCache needs to be positive.' ) # pragma: no cover.
119- self .capacity = capacity
120- self .freq_link_head = None
121- self .lock = Lock ()
17+ raise ValueError ('Capacity of DistanceCache needs to be positive.' ) # pragma: no cover.
18+ self .cache = LRUCache (capacity )
12219
12320 def get (self , key ):
124- with self .lock :
125- if key in self .cache :
126- cache_node = self .cache [key ]
127- freq_node = cache_node .freq_node
128- content = cache_node .content
129-
130- self .move_forward (cache_node , freq_node )
131-
132- return content
133- else :
134- return not_found
21+ return self .cache .get (key , not_found )
13522
13623 def set (self , key , report_type = None , value = None ):
137- with self .lock :
138- if key in self .cache :
139- cache_node = self .cache [key ]
140- if report_type :
141- cache_node .content [report_type ].add (value )
142- else :
143- cache_node .content = value
144- else :
145- if len (self .cache ) >= self .capacity :
146- self .dump_cache ()
147-
148- self .create_cache_node (key , report_type , value )
24+ if report_type :
25+ content = self .cache .get (key , None )
26+ if content is None :
27+ content = defaultdict (SetOrdered )
28+ content [report_type ].add (value )
29+ value = content
30+ self .cache .insert (key , value )
14931
15032 def __contains__ (self , key ):
15133 return key in self .cache
15234
153- def move_forward (self , cache_node , freq_node ):
154- if freq_node .nxt is None or freq_node .nxt .freq != freq_node .freq + 1 :
155- target_freq_node = FreqNode (freq_node .freq + 1 , None , None )
156- target_empty = True
157- else :
158- target_freq_node = freq_node .nxt
159- target_empty = False
160-
161- cache_node .free_myself ()
162- target_freq_node .append_cache_to_tail (cache_node )
163-
164- if target_empty :
165- freq_node .insert_after_me (target_freq_node )
166-
167- if freq_node .count_caches () == 0 :
168- if self .freq_link_head == freq_node :
169- self .freq_link_head = target_freq_node
170-
171- freq_node .remove ()
17235
173- def dump_cache (self ):
174- head_freq_node = self .freq_link_head
175- self .cache .pop (head_freq_node .cache_head .key ) # type: ignore
176- head_freq_node .pop_head_cache () # type: ignore
177-
178- if head_freq_node .count_caches () == 0 : # type: ignore
179- self .freq_link_head = head_freq_node .nxt # type: ignore
180- head_freq_node .remove () # type: ignore
181-
182- def create_cache_node (self , key , report_type , value ):
183- cache_node = CacheNode (
184- key = key , report_type = report_type ,
185- value = value , freq_node = None , pre = None , nxt = None )
186- self .cache [key ] = cache_node
187-
188- if self .freq_link_head is None or self .freq_link_head .freq != 0 :
189- new_freq_node = FreqNode (0 , None , None )
190- new_freq_node .append_cache_to_tail (cache_node )
191-
192- if self .freq_link_head is not None :
193- self .freq_link_head .insert_before_me (new_freq_node )
194-
195- self .freq_link_head = new_freq_node
196- else :
197- self .freq_link_head .append_cache_to_tail (cache_node )
198-
199- def get_sorted_cache_keys (self ):
200- result = [(i , freq .freq_node .freq ) for i , freq in self .cache .items ()]
201- result .sort (key = lambda x : - x [1 ])
202- return result
203-
204- def get_average_frequency (self ):
205- return mean (freq .freq_node .freq for freq in self .cache .values ())
36+ LFUCache = DistanceCache
20637
20738
20839class DummyLFU :
@@ -211,7 +42,9 @@ def __init__(self, *args, **kwargs):
21142 pass
21243
21344 set = __init__
214- get = __init__
45+
46+ def get (self , * args , ** kwargs ):
47+ return not_found
21548
21649 def __contains__ (self , key ):
21750 return False
0 commit comments