暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

用 python 实现一个原生的 LRU 算法

鸡仔说 2021-03-01
1311

上一节鸡仔说 LRU 通常设计成散列表+双链表的结构,那么为什么要这样设计呢?

首先,LRU 是一种缓存淘汰算法,它的目的是实现数据的高速访问。在访问或者计算某一个数据的成本太高时,应该在首次访问时,将数据先缓存起来,以待后续被重复利用。基于此,LRU 要满足查询效率尽可能的高,显然,用查询效率是O(1) 的哈希表是再适合不过的了。
我们上一节通过衣架的例子发现,每一次都将今日要穿的衣服放在衣架的最右侧。因此我们看出,LRU 的数据储存是有序的,数组和链表都是有序的,而如果衣架上已经有我们今天穿的衣服了,我们就应该将它拿出来,放在最右侧。这种置换位置对应于数据结构中就是删除与插入操作,数组中由于插入和删除数据涉及数据搬移,时间复杂度为O(n)。而 LRU 中对于数据位置的转移是高频操作,因此需要选择插入和删除都是O(1)的链表结构。
那么我们使用单链表可不可以呢?可以,但成本太高。比如我们在链表中数据已经满的情况下,又来了一个新数据。这个时候我们需要删除我们最近最少使用的数据(假设是在最左侧),然后将新数据插入最右侧。如果是单链表,我们需要访问整个链表,时间复杂度为 O(n)。而如果是双链表,我们则能够在 O(1) 的时间复杂度内完成,但凡事都是有代价,这种提高访问速度的方式是以空间换来的。因为散列表中存了 n 个 key,而链表中,存储了 n 个 key 对应的 value。

talk is cheap, show me the code. 
import timeclass Node:    # Nodes are represented in n    def __init__(self, key, val):        self.key = key        self.val = val        self.next = None        self.prev = Noneclass LRUCache:    cache_limit = None    # if the DEBUG is TRUE then it    # will execute    DEBUG = False    def __init__(self, func):        self.func = func        self.cache = {}        self.head = Node(00)        self.tail = Node(00)        self.head.next = self.tail        self.tail.prev = self.head    def __call__(self, *args, **kwargs):        # The cache presents with the help        # of Linked List        if args in self.cache:            self.llist(args)            if self.DEBUG:                return f'Cached...{args}\n{self.cache[args]}\nCache: {self.cache}'            return self.cache[args]            # The given cache keeps on moving.        if self.cache_limit is not None:            if len(self.cache) > self.cache_limit:                n = self.head.next                self._remove(n)                del self.cache[n.key]                # Compute and cache and node to see whether        # the following element is present or not        # based on the given input.        result = self.func(*args, **kwargs)        self.cache[args] = result        node = Node(args, result)        self._add(node)        if self.DEBUG:            return f'{result}\nCache: {self.cache}'        return result        # Remove from double linked-list - Node.    def _remove(self, node):        p = node.prev        n = node.next        p.next = n        n.prev = p        # Add to double linked-list - Node.    def _add(self, node):        p = self.tail.prev        p.next = node        self.tail.prev = node        node.prev = p        node.next = self.tail        # Over here the result task is being done    def llist(self, args):        current = self.head        while True:            if current.key == args:                node = current                self._remove(node)                self._add(node)                if self.DEBUG:                    del self.cache[node.key]                    self.cache[node.key] = node.val                break            else:                current = current.next# Default Debugging is FALSE. For# execution of DEBUG is set to TRUELRUCache.DEBUG = True# The DEFAULT test limit is NONE.LRUCache.cache_limit = 3@LRUCachedef ex_func_01(n):    print(f'Computing...{n}')    time.sleep(1)    return nif __name__ == '__main__':    print(f'\nFunction: ex_func_01')    print(ex_func_01(1))    print(ex_func_01(2))    print(ex_func_01(3))    print(ex_func_01(4))    print(ex_func_01(1))    print(ex_func_01(2))    print(ex_func_01(5))    print(ex_func_01(1))    print(ex_func_01(2))    print(ex_func_01(3))    print(ex_func_01(4))    print(ex_func_01(5))
上述代码通过字典+双链表的结构实现了 LRU,但其实 Python 内置模块提供了有序字典的结构,能够轻松实现 LRU,你可以尝试自动实现下。Python 从 3.2 之后,提供了内置的 LRU 模块。使用起来非常方便,下一节鸡仔就和大家一起,实现一个基于知乎热榜爬虫的 LRU 小项目。bye~
参考资料:

[1] user3586940 (2019). Why Use A Doubly Linked List and HashMap for a LRU Cache Instead of a Deque?

https://stackoverflow.com/questions/54730706/why-use-a-doubly-linked-list-and-hashmap-for-a-lru-cache-instead-of-a-deque

[2] Jose Alberto Torres Agüera(2019). Python and LRU Cache.

https://medium.com/lambda-automotive/python-and-lru-cache-f812bbdcbb51

[3] Santiago Valdarrama(2020). Caching in Python Using the LRU Cache Strategy.

https://realpython.com/lru-cache-python/#adding-cache-expiration

[4] Cameron MacLeod(2019). Easy Python speed wins with functools.lru_cache.

https://www.cameronmacleod.com/blog/python-lru-cache

[5] Cake Labs(2021). LRU Cache.

https://www.interviewcake.com/concept/java/lru-cache

[6] sakshiparikh23(2020). Python – LRU Cache

https://www.geeksforgeeks.org/python-lru-cache/

以上,如果觉得内容对你有所帮助,还请点个「在看」支持,谢谢各位dai佬!

好看的人都点了在看


文章转载自鸡仔说,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论