157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
|
|
"""
|
||
|
|
A fork of Python 3.6's stdlib lru_cache (found in Python's 'cpython/Lib/functools.py')
|
||
|
|
adapted into a data structure for single threaded uses.
|
||
|
|
|
||
|
|
https://github.com/python/cpython/blob/v3.6.12/Lib/functools.py
|
||
|
|
|
||
|
|
|
||
|
|
Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||
|
|
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
|
||
|
|
|
||
|
|
All Rights Reserved
|
||
|
|
|
||
|
|
|
||
|
|
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||
|
|
--------------------------------------------
|
||
|
|
|
||
|
|
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||
|
|
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||
|
|
otherwise using this software ("Python") in source or binary form and
|
||
|
|
its associated documentation.
|
||
|
|
|
||
|
|
2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||
|
|
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||
|
|
analyze, test, perform and/or display publicly, prepare derivative works,
|
||
|
|
distribute, and otherwise use Python alone or in any derivative version,
|
||
|
|
provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||
|
|
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||
|
|
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation;
|
||
|
|
All Rights Reserved" are retained in Python alone or in any derivative version
|
||
|
|
prepared by Licensee.
|
||
|
|
|
||
|
|
3. In the event Licensee prepares a derivative work that is based on
|
||
|
|
or incorporates Python or any part thereof, and wants to make
|
||
|
|
the derivative work available to others as provided herein, then
|
||
|
|
Licensee hereby agrees to include in any such work a brief summary of
|
||
|
|
the changes made to Python.
|
||
|
|
|
||
|
|
4. PSF is making Python available to Licensee on an "AS IS"
|
||
|
|
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||
|
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||
|
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||
|
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||
|
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||
|
|
|
||
|
|
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||
|
|
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||
|
|
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||
|
|
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||
|
|
|
||
|
|
6. This License Agreement will automatically terminate upon a material
|
||
|
|
breach of its terms and conditions.
|
||
|
|
|
||
|
|
7. Nothing in this License Agreement shall be deemed to create any
|
||
|
|
relationship of agency, partnership, or joint venture between PSF and
|
||
|
|
Licensee. This License Agreement does not grant permission to use PSF
|
||
|
|
trademarks or trade name in a trademark sense to endorse or promote
|
||
|
|
products or services of Licensee, or any third party.
|
||
|
|
|
||
|
|
8. By copying, installing or otherwise using Python, Licensee
|
||
|
|
agrees to be bound by the terms and conditions of this License
|
||
|
|
Agreement.
|
||
|
|
|
||
|
|
"""
|
||
|
|
|
||
|
|
SENTINEL = object()
|
||
|
|
|
||
|
|
|
||
|
|
# aliases to the entries in a node
|
||
|
|
PREV = 0
|
||
|
|
NEXT = 1
|
||
|
|
KEY = 2
|
||
|
|
VALUE = 3
|
||
|
|
|
||
|
|
|
||
|
|
class LRUCache(object):
|
||
|
|
def __init__(self, max_size):
|
||
|
|
assert max_size > 0
|
||
|
|
|
||
|
|
self.max_size = max_size
|
||
|
|
self.full = False
|
||
|
|
|
||
|
|
self.cache = {}
|
||
|
|
|
||
|
|
# root of the circularly linked list to keep track of
|
||
|
|
# the least recently used key
|
||
|
|
self.root = [] # type: ignore
|
||
|
|
# the node looks like [PREV, NEXT, KEY, VALUE]
|
||
|
|
self.root[:] = [self.root, self.root, None, None]
|
||
|
|
|
||
|
|
self.hits = self.misses = 0
|
||
|
|
|
||
|
|
def set(self, key, value):
|
||
|
|
link = self.cache.get(key, SENTINEL)
|
||
|
|
|
||
|
|
if link is not SENTINEL:
|
||
|
|
# have to move the node to the front of the linked list
|
||
|
|
link_prev, link_next, _key, _value = link
|
||
|
|
|
||
|
|
# first remove the node from the lsnked list
|
||
|
|
link_prev[NEXT] = link_next
|
||
|
|
link_next[PREV] = link_prev
|
||
|
|
|
||
|
|
# insert the node between the root and the last
|
||
|
|
last = self.root[PREV]
|
||
|
|
last[NEXT] = self.root[PREV] = link
|
||
|
|
link[PREV] = last
|
||
|
|
link[NEXT] = self.root
|
||
|
|
|
||
|
|
# update the value
|
||
|
|
link[VALUE] = value
|
||
|
|
|
||
|
|
elif self.full:
|
||
|
|
# reuse the root node, so update its key/value
|
||
|
|
old_root = self.root
|
||
|
|
old_root[KEY] = key
|
||
|
|
old_root[VALUE] = value
|
||
|
|
|
||
|
|
self.root = old_root[NEXT]
|
||
|
|
old_key = self.root[KEY]
|
||
|
|
|
||
|
|
self.root[KEY] = self.root[VALUE] = None
|
||
|
|
|
||
|
|
del self.cache[old_key]
|
||
|
|
|
||
|
|
self.cache[key] = old_root
|
||
|
|
|
||
|
|
else:
|
||
|
|
# insert new node after last
|
||
|
|
last = self.root[PREV]
|
||
|
|
link = [last, self.root, key, value]
|
||
|
|
last[NEXT] = self.root[PREV] = self.cache[key] = link
|
||
|
|
self.full = len(self.cache) >= self.max_size
|
||
|
|
|
||
|
|
def get(self, key, default=None):
|
||
|
|
link = self.cache.get(key, SENTINEL)
|
||
|
|
|
||
|
|
if link is SENTINEL:
|
||
|
|
self.misses += 1
|
||
|
|
return default
|
||
|
|
|
||
|
|
# have to move the node to the front of the linked list
|
||
|
|
link_prev, link_next, _key, _value = link
|
||
|
|
|
||
|
|
# first remove the node from the lsnked list
|
||
|
|
link_prev[NEXT] = link_next
|
||
|
|
link_next[PREV] = link_prev
|
||
|
|
|
||
|
|
# insert the node between the root and the last
|
||
|
|
last = self.root[PREV]
|
||
|
|
last[NEXT] = self.root[PREV] = link
|
||
|
|
link[PREV] = last
|
||
|
|
link[NEXT] = self.root
|
||
|
|
|
||
|
|
self.hits += 1
|
||
|
|
|
||
|
|
return link[VALUE]
|