[python] 효율적인 순환 버퍼?

효율적인 순환 버퍼 를 만들고 싶습니다.파이썬에서 ( 에서 정수 값의 평균을 구하는 것을 목표로).

목록을 사용하여 값을 수집하는 효율적인 방법입니까?

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

무엇이 더 효율적일까요 (그리고 왜)?



답변

나는 arg collections.deque와 함께 사용할 것 입니다.maxlen

>>> 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이 당신이 원하는 유사하다가. 이것이 가장 효율적이라는 나의 주장은 전적으로 최고 수준의 코드를 작성하는 습관을 가진 믿을 수 없을 정도로 숙련 된 승무원에 의해 C로 구현되었다는 사실에 있습니다.


답변

목록의 헤드에서 팝하면 전체 목록이 복사되므로 비효율적입니다.

대신 고정 된 크기의 목록 / 배열과 항목을 추가 / 제거 할 때 버퍼를 통해 이동하는 인덱스를 사용해야합니다.


답변

MoonCactus의 답변을 바탕으로 여기에 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, data = []):
        """Initialization"""
        self.index = 0
        self.size = size
        self._data = list(data)[-size:]

    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)'

[편집 됨] :data 기존 목록에서 초기화를 허용하는 선택적 매개 변수 추가 , 예 :

circularlist(4, [1, 2, 3, 4, 5])      #  [2, 3, 4, 5] (4 items)
circularlist(4, set([1, 2, 3, 4, 5])) #  [2, 3, 4, 5] (4 items)
circularlist(4, (1, 2, 3, 4, 5))      #  [2, 3, 4, 5] (4 items)


답변

Python의 deque는 느립니다. 대신 numpy.roll을 사용할 수도 있습니다.
(n,) 또는 (n, 1)의 숫자 배열에서 숫자 어떻게 회전합니까?

이 벤치 마크에서 deque는 448ms입니다. Numpy.roll은 29ms입니다.
http://scimusing.wordpress.com/2013/10/25/ring-buffers-in-pythonnumpy/


답변

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


답변

여기에는 이미 많은 훌륭한 답변이 있지만 언급 된 옵션에 대한 타이밍의 직접적인 비교를 찾을 수 없습니다. 따라서 아래 비교에서 저의 겸손한 시도를 찾으십시오.

테스트 목적으로 만 클래스는 list기반 버퍼, collections.deque기반 버퍼 및 기반 버퍼 간에 전환 할 수 있습니다 Numpy.roll.

update메서드는 단순하게 유지하기 위해 한 번에 하나의 값만 추가합니다.

import numpy
import timeit
import collections


class CircularBuffer(object):
    buffer_methods = ('list', 'deque', 'roll')

    def __init__(self, buffer_size, buffer_method):
        self.content = None
        self.size = buffer_size
        self.method = buffer_method

    def update(self, scalar):
        if self.method == self.buffer_methods[0]:
            # Use list
            try:
                self.content.append(scalar)
                self.content.pop(0)
            except AttributeError:
                self.content = [0.] * self.size
        elif self.method == self.buffer_methods[1]:
            # Use collections.deque
            try:
                self.content.append(scalar)
            except AttributeError:
                self.content = collections.deque([0.] * self.size,
                                                 maxlen=self.size)
        elif self.method == self.buffer_methods[2]:
            # Use Numpy.roll
            try:
                self.content = numpy.roll(self.content, -1)
                self.content[-1] = scalar
            except IndexError:
                self.content = numpy.zeros(self.size, dtype=float)

# Testing and Timing
circular_buffer_size = 100
circular_buffers = [CircularBuffer(buffer_size=circular_buffer_size,
                                   buffer_method=method)
                    for method in CircularBuffer.buffer_methods]
timeit_iterations = 1e4
timeit_setup = 'from __main__ import circular_buffers'
timeit_results = []
for i, cb in enumerate(circular_buffers):
    # We add a convenient number of convenient values (see equality test below)
    code = '[circular_buffers[{}].update(float(j)) for j in range({})]'.format(
        i, circular_buffer_size)
    # Testing
    eval(code)
    buffer_content = [item for item in cb.content]
    assert buffer_content == range(circular_buffer_size)
    # Timing
    timeit_results.append(
        timeit.timeit(code, setup=timeit_setup, number=int(timeit_iterations)))
    print '{}: total {:.2f}s ({:.2f}ms per iteration)'.format(
        cb.method, timeit_results[-1],
        timeit_results[-1] / timeit_iterations * 1e3)

내 시스템에서 이것은 다음을 산출합니다.

list:  total 1.06s (0.11ms per iteration)
deque: total 0.87s (0.09ms per iteration)
roll:  total 6.27s (0.63ms per iteration)


답변

방법에 대한 파이썬 요리 책에서 솔루션 이 가득 차게 링 버퍼 인스턴스의 재 분류를 포함?

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이 레시피 와 같은 두 개의 클래식 클래스에서 잘 작동합니다 ).

인스턴스의 클래스를 변경하는 것은 많은 언어에서 이상 할 수 있지만,이 레시피에서와 같이 동작에 막대한 영향을 미치는 경우에 따라 대규모, 비가 역적, 개별적인 상태 변경을 나타내는 다른 방법에 대한 Python의 대안입니다. 파이썬이 모든 종류의 클래스를 지원한다는 것은 좋은 점입니다.

크레딧 : Sébastien Keim