Python实现LRU算法的2种方法


Posted in Python onJune 24, 2015

LRU:least recently used,最近最少使用算法。它的使用场景是:在有限的空间中存储对象时,当空间满时,会按一定的原则删除原有的对象,常用的原则(算法)有LRU,FIFO,LFU等。在计算机的Cache硬件,以及主存到虚拟内存的页面置换,还有Redis缓存系统中都用到了该算法。我在一次面试和一个笔试时,也遇到过这个问题。

LRU的算法是比较简单的,当对key进行访问时(一般有查询,更新,增加,在get()和set()两个方法中实现即可)时,将该key放到队列的最前端(或最后端)就行了,这样就实现了对key按其最后一次访问的时间降序(或升序)排列,当向空间中增加新对象时,如果空间满了,删除队尾(或队首)的对象。

在Python中,可以使用collections.OrderedDict很方便的实现LRU算法,当然,如果你想不到用OrderedDict,那可以用dict+list来实现。本文主要参考了LRU CACHE IN PYTHON,写的非常好,既实现了功能,又简洁易读。方法一的代码与参考文章基本相同,方法二是我自己想出来的,比较繁琐一些,其实OrderedDict本身也是类似的这种机制来实现的有序。

不过,下面的实现是有问题的,这个cache的key:value键值对中,value只能是不可变类型。因为,如果value是可变类型,那对于同一个key,所有调用get(key)方法返回的value都是指向同一个可变对象的,当修改其中一个value时,那所有的value都会被修改了,即使你没有调用set()方法也会这样。这是我们不希望看到的。解决方法我想到了两种,一是可变对象序列化后再存储,即将可变对象转为不可变对象;二是仍存储可变对象,但get()时,返回一个深拷贝,这样每个get()调用返回的对象就不会相互影响了。推荐第一种方法。另外,对于key,推荐使用str/unicode类型。

当并发时,还会存在一个问题,因为这涉及到对公共资源的写操作,所以必须要对set()加锁。其实,在并发情况下,所有对公共资源的写操作都要加锁。如果不存在并发的情况,只有单线程,那可以不加锁。

方法一:用OrderedDict实现(推荐)

from collections import OrderedDict

 

 

class LRUCache(OrderedDict):

    '''不能存储可变类型对象,不能并发访问set()'''
    def __init__(self,capacity):

        self.capacity = capacity

        self.cache = OrderedDict()

    
    def get(self,key):

        if self.cache.has_key(key):

            value = self.cache.pop(key)

            self.cache[key] = value

        else:

            value = None

         

        return value

    
    def set(self,key,value):

        if self.cache.has_key(key):

            value = self.cache.pop(key)

            self.cache[key] = value

        else:

            if len(self.cache) == self.capacity:

                self.cache.popitem(last = False)    #pop出第一个item

                self.cache[key] = value

            else:

                self.cache[key] = value

测试代码如下
c = LRUCache(5)

  

for i in range(5,10):

    c.set(i,10*i)

  

  

print c.cache, c.cache.keys()

  

c.get(5)

c.get(7)

  

print c.cache, c.cache.keys()

  

c.set(10,100)

print c.cache, c.cache.keys()

  

c.set(9,44)

print c.cache, c.cache.keys()

输出如下

OrderedDict([(5, 50), (6, 60), (7, 70), (8, 80), (9, 90)])     [5, 6, 7, 8, 9]

OrderedDict([(6, 60), (8, 80), (9, 90), (5, 50), (7, 70)])     [6, 8, 9, 5, 7]

OrderedDict([(8, 80), (9, 90), (5, 50), (7, 70), (10, 100)])   [8, 9, 5, 7, 10]

OrderedDict([(8, 80), (5, 50), (7, 70), (10, 100), (9, 90)])   [8, 5, 7, 10, 9]

方法二:用dict+list实现(不推荐)

class LRUCache(object):

    '''不能存储可变类型对象,不能并发访问set()'''

  

    def __init__(self,capacity):

        self.l = []

        self.d = {}

        self.capacity = capacity

         
    def get(self,key):

        if self.d.has_key(key):

            value = self.d[key]

            self.l.remove(key)

            self.l.insert(0,key)

        else:

            value = None

          

        return value

     
    def set(self,key,value):

        if self.d.has_key(key):

            self.l.remove(key)

        elif len(self.d) == self.capacity:

                oldest_key = self.l.pop()

                self.d.pop(oldest_key)

                  

        self.d[key] = value

        self.l.insert(0, key)

测试代码如下
c = LRUCache(5)

  

for i in range(5,10):

    c.set(i,10*i)

  

  

print c.d,c.l

  

c.get(5)

c.get(7)

  

print c.d,c.l

  

c.set(10,100)

print c.d,c.l

  

c.set(9,44)

print c.d,c.l

输出为

{8: 80, 9: 90, 5: 50, 6: 60, 7: 70}   [9, 8, 7, 6, 5]

{8: 80, 9: 90, 5: 50, 6: 60, 7: 70}   [7, 5, 9, 8, 6]

{5: 50, 7: 70, 8: 80, 9: 90, 10: 100} [10, 7, 5, 9, 8]

{5: 50, 7: 70, 8: 80, 9: 44, 10: 100} [9, 10, 7, 5, 8]
Python 相关文章推荐
python实现apahce网站日志分析示例
Apr 02 Python
django 修改server端口号的方法
May 14 Python
用Python写一个模拟qq聊天小程序的代码实例
Mar 06 Python
PyQt5实现从主窗口打开子窗口的方法
Jun 19 Python
python3射线法判断点是否在多边形内
Jun 28 Python
pycharm运行scrapy过程图解
Nov 22 Python
Python基础之字符串常见操作经典实例详解
Feb 26 Python
利用python如何实现猫捉老鼠小游戏
Dec 04 Python
Python中正则表达式对单个字符,多个字符和匹配边界等使用
Jan 27 Python
Django与AJAX实现网页动态数据显示的示例代码
Feb 24 Python
Python3自带工具2to3.py 转换 Python2.x 代码到Python3的操作
Mar 03 Python
教你怎么用Python操作MySql数据库
May 31 Python
Python中线程编程之threading模块的使用详解
Jun 23 #Python
Python Property属性的2种用法
Jun 21 #Python
Python中实现三目运算的方法
Jun 21 #Python
Python中有趣在__call__函数
Jun 21 #Python
Python的装饰器模式与面向切面编程详解
Jun 21 #Python
Python安装第三方库的3种方法
Jun 21 #Python
Python实现线程池代码分享
Jun 21 #Python
You might like
PHP捕获Fatal error错误的方法
2014/06/11 PHP
PHP简单装饰器模式实现与用法示例
2017/06/22 PHP
漂亮的widgets,支持换肤和后期开发新皮肤
2007/04/23 Javascript
精通Javascript系列之Javascript基础篇
2011/06/07 Javascript
基于jQuery的判断iPad、iPhone、Android是横屏还是竖屏的代码
2014/05/11 Javascript
javascript操作字符串的原生方法
2014/12/22 Javascript
js函数内变量的作用域分析
2015/01/12 Javascript
了解Javascript的模块化开发
2015/03/02 Javascript
js中javascript:void(0) 真正含义
2020/11/05 Javascript
JavaScript转换与解析JSON方法实例详解
2015/11/24 Javascript
jquery实现列表上下移动功能
2016/02/25 Javascript
移动端日期插件Mobiscroll.js使用详解
2016/12/19 Javascript
详解vue-Resource(与后端数据交互)
2017/01/16 Javascript
JS实现动态修改table及合并单元格的方法示例
2017/02/20 Javascript
vue-router 学习快速入门
2017/03/01 Javascript
php输出全部gb2312编码内的汉字方法
2017/03/04 Javascript
JavaScript仿微信打飞机游戏
2020/07/05 Javascript
Angular2使用jQuery的方法教程
2017/05/28 jQuery
vue 实现微信浮标效果
2019/09/01 Javascript
antd-mobile ListView长列表的数据更新遇到的坑
2020/04/08 Javascript
Vue axios 跨域请求无法带上cookie的解决
2020/09/08 Javascript
JavaScript中常用的3种弹出提示框(alert、confirm、prompt)
2020/11/10 Javascript
js异步接口并发数量控制的方法示例
2020/11/22 Javascript
[52:52]完美世界DOTA2联赛PWL S3 LBZS vs access 第一场 12.10
2020/12/13 DOTA
python集合类型用法分析
2015/04/08 Python
Django web框架使用url path name详解
2019/04/29 Python
python多进程下实现日志记录按时间分割
2019/07/22 Python
python入门之井字棋小游戏
2020/03/05 Python
opencv 形态学变换(开运算,闭运算,梯度运算)
2020/07/07 Python
如何用PyPy让你的Python代码运行得更快
2020/12/02 Python
python爬虫利器之requests库的用法(超全面的爬取网页案例)
2020/12/17 Python
Sneaker Studio法国:购买运动鞋
2018/06/08 全球购物
辞职申请书范本
2019/05/20 职场文书
MySQL安装后默认自带数据库的作用详解
2021/04/27 MySQL
端午节将至,用Python爬取粽子数据并可视化,看看网友喜欢哪种粽子吧!
2021/06/11 Python
Windows Server 2012 修改远程默认端口3389的方法
2022/04/28 Servers