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中的异常处理简明介绍
Apr 13 Python
利用python画出折线图
Jul 26 Python
python抓取京东小米8手机配置信息
Nov 13 Python
利用python GDAL库读写geotiff格式的遥感影像方法
Nov 29 Python
对python For 循环的三种遍历方式解析
Feb 01 Python
PyQT5 QTableView显示绑定数据的实例详解
Jun 25 Python
pytorch 在网络中添加可训练参数,修改预训练权重文件的方法
Aug 17 Python
在tensorflow中实现去除不足一个batch的数据
Jan 20 Python
Python关于反射的实例代码分享
Feb 20 Python
详解django使用include无法跳转的解决方法
Mar 19 Python
高考考python编程是真的吗
Jul 20 Python
Python中lru_cache的使用和实现详解
Jan 25 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
Discuz 6.0+ 批量注册用户名
2009/09/13 PHP
PHP实现根据设备类型自动跳转相应页面的方法
2014/07/24 PHP
php实现删除空目录的方法
2015/03/16 PHP
yii2 modal弹窗之ActiveForm ajax表单异步验证
2016/06/13 PHP
php获取数据库结果集方法(推荐)
2017/06/01 PHP
PHP语言对接抖音快手小红书视频/图片去水印API接口源码
2020/08/11 PHP
Alliance vs Liquid BO3 第一场2.13
2021/03/10 DOTA
showModelessDialog()使用详解
2006/09/21 Javascript
javascript KeyDown、KeyPress和KeyUp事件的区别与联系
2009/12/03 Javascript
JQuery Tips(2) 关于$()包装集你不知道的
2009/12/14 Javascript
javascript 导出数据到Excel(处理table中的元素)
2009/12/18 Javascript
妙用Jquery的val()方法
2012/06/27 Javascript
js实现动态添加、删除行、onkeyup表格求和示例
2013/08/18 Javascript
javascript计算用户打开网页的停留时间
2014/01/09 Javascript
Extjs中RowExpander控件的默认展开问题示例探讨
2014/01/24 Javascript
第一次接触Bootstrap框架
2016/10/24 Javascript
jQuery实现花式轮播之圣诞节礼物传送效果
2016/12/25 Javascript
dropload.js插件下拉刷新和上拉加载使用详解
2017/10/20 Javascript
vue cli3.0结合echarts3.0与地图的使用方法示例
2019/03/26 Javascript
阿望教你用vue写扫雷小游戏
2020/01/20 Javascript
Python专用方法与迭代机制实例分析
2014/09/15 Python
Python 中开发pattern的string模板(template) 实例详解
2017/04/01 Python
Python实现多并发访问网站功能示例
2017/06/19 Python
python字符串的方法与操作大全
2018/01/30 Python
JS设计模式之责任链模式实例详解
2018/02/03 Python
python环境路径配置以及命令行运行脚本
2019/04/02 Python
使用python将excel数据导入数据库过程详解
2019/08/27 Python
如何关掉pycharm中的python console(图解)
2019/10/31 Python
keras 自定义loss model.add_loss的使用详解
2020/06/22 Python
python脚本和网页有何区别
2020/07/02 Python
在 Windows 下搭建高效的 django 开发环境的详细教程
2020/07/27 Python
python re的findall和finditer的区别详解
2020/11/15 Python
优秀管理者事迹材料
2014/05/22 职场文书
预备党员综合考察材料
2014/05/31 职场文书
教师职位说明书
2014/07/29 职场文书