有效的循环缓冲?

时间:2010-11-11 04:17:18

标签: python circular-buffer

我想在python中创建一个高效的circular buffer(目标是取缓冲区中整数值的平均值)。

这是使用列表收集值的有效方法吗?

def add_to_buffer( self, num ):
    self.mylist.pop( 0 )
    self.mylist.append( num )

什么会更有效(以及为什么)?

15 个答案:

答案 0 :(得分:170)

我会将collections.dequemaxlen arg

一起使用
>>> import collections
>>> d = collections.deque(maxlen=10)
>>> d
deque([], maxlen=10)
>>> for i in xrange(20):
...     d.append(i)
... 
>>> d
deque([10, 11, 12, 13, 14, 15, 16, 17, 18, 19], maxlen=10)

deque的文档中有recipe,与您想要的类似。我断言它是最有效的,完全取决于它是由一个非常熟练的工作人员在C中实现的,这种工作人员习惯于开出顶级代码。

答案 1 :(得分:11)

从列表的头部弹出会导致整个列表被复制,因此效率低下

您应该使用固定大小的列表/数组和在添加/删除项目时在缓冲区中移动的索引

答案 2 :(得分:8)

Python的deque很慢。您也可以使用numpy.roll How do you rotate the numbers in an numpy array of shape (n,) or (n,1)?

在此基准测试中,deque为448ms。 Numpy.roll是29ms http://scimusing.wordpress.com/2013/10/25/ring-buffers-in-pythonnumpy/

答案 3 :(得分:7)

基于MoonCactus's answer,这是一个circularlist类。与他的版本的不同之处在于,c[0]将始终提供最旧的附加元素,c[-1]最新附加的元素,倒数第二个c[-2] ...这对于应用程序来说更自然。

c = circularlist(4)
c.append(1); print c, c[0], c[-1]    #[1]              1, 1
c.append(2); print c, c[0], c[-1]    #[1, 2]           1, 2
c.append(3); print c, c[0], c[-1]    #[1, 2, 3]        1, 3
c.append(8); print c, c[0], c[-1]    #[1, 2, 3, 8]     1, 8
c.append(10); print c, c[0], c[-1]   #[10, 2, 3, 8]    2, 10
c.append(11); print c, c[0], c[-1]   #[10, 11, 3, 8]   3, 11

类别:

class circularlist(object):
    def __init__(self, size):
        """Initialization"""
        self.index = 0
        self.size = size
        self._data = []

    def append(self, value):
        """Append an element"""
        if len(self._data) == self.size:
            self._data[self.index] = value
        else:
            self._data.append(value)
        self.index = (self.index + 1) % self.size

    def __getitem__(self, key):
        """Get element by index, relative to the current index"""
        if len(self._data) == self.size:
            return(self._data[(key + self.index) % self.size])
        else:
            return(self._data[key])

    def __repr__(self):
        """Return string representation"""
        return self._data.__repr__() + ' (' + str(len(self._data))+' items)'

答案 4 :(得分:5)

确定使用deque类,但对于问题的要求(平均值),这是我的解决方案:

>>> from collections import deque
>>> class CircularBuffer(deque):
...     def __init__(self, size=0):
...             super(CircularBuffer, self).__init__(maxlen=size)
...     @property
...     def average(self):  # TODO: Make type check for integer or floats
...             return sum(self)/len(self)
...
>>>
>>> cb = CircularBuffer(size=10)
>>> for i in range(20):
...     cb.append(i)
...     print "@%s, Average: %s" % (cb, cb.average)
...
@deque([0], maxlen=10), Average: 0
@deque([0, 1], maxlen=10), Average: 0
@deque([0, 1, 2], maxlen=10), Average: 1
@deque([0, 1, 2, 3], maxlen=10), Average: 1
@deque([0, 1, 2, 3, 4], maxlen=10), Average: 2
@deque([0, 1, 2, 3, 4, 5], maxlen=10), Average: 2
@deque([0, 1, 2, 3, 4, 5, 6], maxlen=10), Average: 3
@deque([0, 1, 2, 3, 4, 5, 6, 7], maxlen=10), Average: 3
@deque([0, 1, 2, 3, 4, 5, 6, 7, 8], maxlen=10), Average: 4
@deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], maxlen=10), Average: 4
@deque([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], maxlen=10), Average: 5
@deque([2, 3, 4, 5, 6, 7, 8, 9, 10, 11], maxlen=10), Average: 6
@deque([3, 4, 5, 6, 7, 8, 9, 10, 11, 12], maxlen=10), Average: 7
@deque([4, 5, 6, 7, 8, 9, 10, 11, 12, 13], maxlen=10), Average: 8
@deque([5, 6, 7, 8, 9, 10, 11, 12, 13, 14], maxlen=10), Average: 9
@deque([6, 7, 8, 9, 10, 11, 12, 13, 14, 15], maxlen=10), Average: 10
@deque([7, 8, 9, 10, 11, 12, 13, 14, 15, 16], maxlen=10), Average: 11
@deque([8, 9, 10, 11, 12, 13, 14, 15, 16, 17], maxlen=10), Average: 12
@deque([9, 10, 11, 12, 13, 14, 15, 16, 17, 18], maxlen=10), Average: 13
@deque([10, 11, 12, 13, 14, 15, 16, 17, 18, 19], maxlen=10), Average: 14

答案 5 :(得分:3)

您还可以看到这个相当古老的Python recipe

这是我自己的NumPy数组版本:

#!/usr/bin/env python

import numpy as np

class RingBuffer(object):
    def __init__(self, size_max, default_value=0.0, dtype=float):
        """initialization"""
        self.size_max = size_max

        self._data = np.empty(size_max, dtype=dtype)
        self._data.fill(default_value)

        self.size = 0

    def append(self, value):
        """append an element"""
        self._data = np.roll(self._data, 1)
        self._data[0] = value 

        self.size += 1

        if self.size == self.size_max:
            self.__class__  = RingBufferFull

    def get_all(self):
        """return a list of elements from the oldest to the newest"""
        return(self._data)

    def get_partial(self):
        return(self.get_all()[0:self.size])

    def __getitem__(self, key):
        """get element"""
        return(self._data[key])

    def __repr__(self):
        """return string representation"""
        s = self._data.__repr__()
        s = s + '\t' + str(self.size)
        s = s + '\t' + self.get_all()[::-1].__repr__()
        s = s + '\t' + self.get_partial()[::-1].__repr__()
        return(s)

class RingBufferFull(RingBuffer):
    def append(self, value):
        """append an element when buffer is full"""
        self._data = np.roll(self._data, 1)
        self._data[0] = value

答案 6 :(得分:3)

虽然这里已有很多很棒的答案,但我找不到所提及方案的时间直接比较。因此,请在下面的比较中找到我的谦虚尝试。

仅出于测试目的,该类可以在基于<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <ul class="list"> <li><a href="">About</a></li> <li><a href="">News</a></li> <li class="clickme"><a href="">Events</a><img class="arrow" src="https://cdn3.iconfinder.com/data/icons/faticons/32/arrow-up-01-512.png" alt=""></li> <ul class="collapsible"> <li><a href="">For Children</a></li> <li><a href="">For Students</a></li> <li><a href="">For Families</a></li> </ul> <li><a href="">Contact</a></li> </ul>的缓冲区,基于 CREATE TABLE IF NOT EXISTS people ( id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT, Lname varchar(20) DEFAULT NULL, Fname varchar(20) DEFAULT NULL, Gender ENUM(‘M’, ’F’), Specialty ENUM(‘1’, ’2’, ’3’, ’4’), Grade ENUM (‘I’, ’J’, ’M’, ’S’), Start_date date DEFAULT NULL, PRIMARY KEY (id) ); 的缓冲区和基于list的缓冲区之间切换。

请注意,collections.deque方法一次只添加一个值,以保持简单。

Numpy.roll

在我的系统上,这会产生:

update

答案 7 :(得分:2)

这个不需要任何库。它会生成一个列表,然后按索引循环。

足迹非常小(没有库),它的运行速度至少是出列的两倍。这对于计算移动平均线确实很好,但请注意,这些项目不按上述年龄排序。

class CircularBuffer(object):
    def __init__(self, size):
        """initialization"""
        self.index= 0
        self.size= size
        self._data = []

    def record(self, value):
        """append an element"""
        if len(self._data) == self.size:
            self._data[self.index]= value
        else:
            self._data.append(value)
        self.index= (self.index + 1) % self.size

    def __getitem__(self, key):
        """get element by index like a regular array"""
        return(self._data[key])

    def __repr__(self):
        """return string representation"""
        return self._data.__repr__() + ' (' + str(len(self._data))+' items)'

    def get_all(self):
        """return a list of all the elements"""
        return(self._data)

获得平均值,例如:

q= CircularBuffer(1000000);
for i in range(40000):
    q.record(i);
print "capacity=", q.size
print "stored=", len(q.get_all())
print "average=", sum(q.get_all()) / len(q.get_all())

结果:

capacity= 1000000
stored= 40000
average= 19999

real 0m0.024s
user 0m0.020s
sys  0m0.000s

这大约相当于出队时间的1/3。

答案 8 :(得分:2)

the solution from the Python Cookbook怎么样,包括在环形缓冲区实例变满时对其进行重新分类?

class RingBuffer:
    """ class that implements a not-yet-full buffer """
    def __init__(self,size_max):
        self.max = size_max
        self.data = []

    class __Full:
        """ class that implements a full buffer """
        def append(self, x):
            """ Append an element overwriting the oldest one. """
            self.data[self.cur] = x
            self.cur = (self.cur+1) % self.max
        def get(self):
            """ return list of elements in correct order """
            return self.data[self.cur:]+self.data[:self.cur]

    def append(self,x):
        """append an element at the end of the buffer"""
        self.data.append(x)
        if len(self.data) == self.max:
            self.cur = 0
            # Permanently change self's class from non-full to full
            self.__class__ = self.__Full

    def get(self):
        """ Return a list of elements from the oldest to the newest. """
        return self.data

# sample usage
if __name__=='__main__':
    x=RingBuffer(5)
    x.append(1); x.append(2); x.append(3); x.append(4)
    print(x.__class__, x.get())
    x.append(5)
    print(x.__class__, x.get())
    x.append(6)
    print(x.data, x.get())
    x.append(7); x.append(8); x.append(9); x.append(10)
    print(x.data, x.get())
  

实施中值得注意的设计选择是,因为这些   对象在某个时刻经历不可逆的状态转换   它们的生命周期-从非完整缓冲区到完整缓冲区(以及行为   更改)。我通过更改self.__class__对此进行了建模。   只要两个类具有相同的功能,即使在Python 2.2中也可以使用   广告位(例如,它可以很好地用于两个经典类,例如   RingBuffer和本食谱中的__Full

     

在许多语言中,更改实例的类可能很奇怪,   但这是其他表示方式的Pythonic替代方法   偶尔,大规模,不可逆转和离散的状态变化   如本食谱所述,对行为有很大影响。 Python的好处   支持所有类型的类。

信用:塞巴斯蒂安·基姆

答案 9 :(得分:1)

我在进行串行编程之前遇到过这个问题。在一年多前的时间里,我也找不到任何有效的实现,所以我最终写了one as a C extension,并且它在MIT许可下也可用on pypi。它是超级基本的,只处理8位有符号字符的缓冲区,但是它具有灵活的长度,因此如果你需要除了字符之外的东西,你可以使用Struct或其上的东西。我现在看到谷歌搜索,这些天有几个选项,所以你可能也想看看那些。

答案 10 :(得分:0)

最初的问题是:“高效”循环缓冲区。 根据这种效率要求,aaronasterling的答案似乎是明确正确的。 使用Python编程的专用类并将时间处理与collections.deque进行比较,显示带有双端队列的x5.2倍加速度! 这是测试它的非常简单的代码:

class cb:
    def __init__(self, size):
        self.b = [0]*size
        self.i = 0
        self.sz = size
    def append(self, v):
        self.b[self.i] = v
        self.i = (self.i + 1) % self.sz

b = cb(1000)
for i in range(10000):
    b.append(i)
# called 200 times, this lasts 1.097 second on my laptop

from collections import deque
b = deque( [], 1000 )
for i in range(10000):
    b.append(i)
# called 200 times, this lasts 0.211 second on my laptop

要将双端队列转换为列表,只需使用:

my_list = [v for v in my_deque]

然后,您将获得O(1)随机访问deque项目。当然,这只有在你设置一次后需要对双端队列进行多次随机访问时才有价值。

答案 11 :(得分:0)

你回答是不对的。 循环缓冲区主有两个原则(https://en.wikipedia.org/wiki/Circular_buffer

  1. 设置缓冲区的长度;
  2. 先进先出;
  3. 当您添加或删除某个项目时,其他项目不应移动其位置
  4. 您的代码如下:

    def add_to_buffer( self, num ):
        self.mylist.pop( 0 )
        self.mylist.append( num )
    

    让我们考虑一下列表已满的情况,使用您的代码:

    self.mylist = [1, 2, 3, 4, 5]
    

    现在我们追加6,列表改为

    self.mylist = [2, 3, 4, 5, 6]
    

    列表中的项目期望1已更改其位置

    您的代码是一个队列,而不是一个圆形缓冲区。

    Basj的答案,我认为是最有效的。

    顺便说一下,圆形缓冲区可以改善操作的性能 添加项目。

答案 12 :(得分:0)

这是将相同的主体应用于一些用于保存最新文本消息的缓冲区。

import time
import datetime
import sys, getopt

class textbffr(object):
    def __init__(self, size_max):
        #initialization
        self.posn_max = size_max-1
        self._data = [""]*(size_max)
        self.posn = self.posn_max

    def append(self, value):
        #append an element
        if self.posn == self.posn_max:
            self.posn = 0
            self._data[self.posn] = value   
        else:
            self.posn += 1
            self._data[self.posn] = value

    def __getitem__(self, key):
        #return stored element
        if (key + self.posn+1) > self.posn_max:
            return(self._data[key - (self.posn_max-self.posn)])
        else:
            return(self._data[key + self.posn+1])


def print_bffr(bffr,bffer_max): 
    for ind in range(0,bffer_max):
        stored = bffr[ind]
        if stored != "":
            print(stored)
    print ( '\n' )

def make_time_text(time_value):
    return(str(time_value.month).zfill(2) + str(time_value.day).zfill(2)
      + str(time_value.hour).zfill(2) +  str(time_value.minute).zfill(2)
      + str(time_value.second).zfill(2))


def main(argv):
    #Set things up 
    starttime = datetime.datetime.now()
    log_max = 5
    status_max = 7
    log_bffr = textbffr(log_max)
    status_bffr = textbffr(status_max)
    scan_count = 1

    #Main Loop
    # every 10 secounds write a line with the time and the scan count.
    while True: 

        time_text = make_time_text(datetime.datetime.now())
        #create next messages and store in buffers
        status_bffr.append(str(scan_count).zfill(6) + " :  Status is just fine at : " + time_text)
        log_bffr.append(str(scan_count).zfill(6) + " : " + time_text + " : Logging Text ")

        #print whole buffers so far
        print_bffr(log_bffr,log_max)
        print_bffr(status_bffr,status_max)

        time.sleep(2)
        scan_count += 1 

if __name__ == '__main__':
    main(sys.argv[1:])  

答案 13 :(得分:0)

来自Github:

class CircularBuffer:

    def __init__(self, size):
        """Store buffer in given storage."""
        self.buffer = [None]*size
        self.low = 0
        self.high = 0
        self.size = size
        self.count = 0

    def isEmpty(self):
        """Determines if buffer is empty."""
        return self.count == 0

    def isFull(self):
        """Determines if buffer is full."""
        return self.count == self.size

    def __len__(self):
        """Returns number of elements in buffer."""
        return self.count

    def add(self, value):
        """Adds value to buffer, overwrite as needed."""
        if self.isFull():
            self.low = (self.low+1) % self.size
        else:
            self.count += 1
        self.buffer[self.high] = value
        self.high = (self.high + 1) % self.size

    def remove(self):
        """Removes oldest value from non-empty buffer."""
        if self.count == 0:
            raise Exception ("Circular Buffer is empty");
        value = self.buffer[self.low]
        self.low = (self.low + 1) % self.size
        self.count -= 1
        return value

    def __iter__(self):
        """Return elements in the circular buffer in order using iterator."""
        idx = self.low
        num = self.count
        while num > 0:
            yield self.buffer[idx]
            idx = (idx + 1) % self.size
            num -= 1

    def __repr__(self):
        """String representation of circular buffer."""
        if self.isEmpty():
            return 'cb:[]'

        return 'cb:[' + ','.join(map(str,self)) + ']'

https://github.com/heineman/python-data-structures/blob/master/2.%20Ubiquitous%20Lists/circBuffer.py

答案 14 :(得分:0)

您可以根据预定义大小的numpy数组检出此circular buffer。这个想法是您创建一个缓冲区(为numpy数组分配内存),然后追加到该缓冲区。数据插入和检索非常快。我创建此模块的目的与您所需的相似。就我而言,我有一个生成整数数据的设备。我读取了数据并将其放在循环缓冲区中,以备将来分析和处理。