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 相关文章推荐
Google开源的Python格式化工具YAPF的安装和使用教程
May 31 Python
Pandas之drop_duplicates:去除重复项方法
Apr 18 Python
Linux(Redhat)安装python3.6虚拟环境(推荐)
May 05 Python
Python爬取qq空间说说的实例代码
Aug 17 Python
Python面向对象程序设计示例小结
Jan 30 Python
解决Python内层for循环如何break出外层的循环的问题
Jun 24 Python
解决python ThreadPoolExecutor 线程池中的异常捕获问题
Apr 08 Python
python 贪心算法的实现
Sep 18 Python
基于Python爬取股票数据过程详解
Oct 21 Python
python实现图片,视频人脸识别(dlib版)
Nov 18 Python
基于Django快速集成Echarts代码示例
Dec 01 Python
Python可视化学习之seaborn调色盘
Feb 24 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判断字符以及字符串的包含方法属性
2008/08/30 PHP
PHP错误提示的关闭方法详解
2013/06/23 PHP
ThinkPHP缓存方法S()概述
2014/06/13 PHP
解析Jquery的LigerUI如何实现文件上传
2013/07/09 Javascript
javaScript如何生成xmlhttp
2013/12/16 Javascript
轻松创建nodejs服务器(2):nodejs服务器的构成分析
2014/12/18 NodeJs
jQuery控制元素显示、隐藏、切换、滑动的方法总结
2015/04/16 Javascript
js检测iframe是否加载完成的方法
2015/11/26 Javascript
prototype框架中美元符号$用法分析
2016/01/22 Javascript
最佳的JavaScript错误处理实践
2016/07/16 Javascript
浅谈Javascript中的12种DOM节点类型
2016/08/19 Javascript
手机软键盘弹出时影响布局的解决方法
2016/12/15 Javascript
JavaScript对象拷贝与Object.assign用法实例分析
2018/06/20 Javascript
BootStrap模态框闪退问题实例代码详解
2018/12/10 Javascript
微信小程序事件对象中e.target和e.currentTarget的区别详解
2019/05/08 Javascript
Layui 带多选框表格监听事件以及按钮自动点击写法实例
2019/09/02 Javascript
vue 使用鼠标滚动加载数据的例子
2019/10/31 Javascript
Vue路由 重定向和别名的区别说明
2020/09/09 Javascript
Python单元测试框架unittest使用方法讲解
2015/04/13 Python
批量获取及验证HTTP代理的Python脚本
2017/04/23 Python
解决python3 网络请求路径包含中文的问题
2018/05/10 Python
python将一个英文语句以单词为单位逆序排放的方法
2018/12/20 Python
Python模块、包(Package)概念与用法分析
2019/05/31 Python
python被修饰的函数消失问题解决(基于wraps函数)
2019/11/04 Python
Python decorator拦截器代码实例解析
2020/04/04 Python
python删除指定列或多列单个或多个内容实例
2020/06/28 Python
美国庭院家具购物网站:AlphaMarts
2019/04/10 全球购物
统计员岗位职责
2013/11/14 职场文书
大学生村官心得体会范文
2014/01/04 职场文书
yy结婚证婚词
2014/01/10 职场文书
服务生自我鉴定
2014/01/22 职场文书
幼儿园端午节活动方案
2014/08/25 职场文书
2014年教研员工作总结
2014/12/23 职场文书
新郎婚礼答谢词
2015/01/04 职场文书
民主生活会意见
2015/06/05 职场文书
深入浅析Redis 集群伸缩原理
2021/05/15 Redis