บัฟเฟอร์วงกลมที่มีประสิทธิภาพ?


109

ฉันต้องการสร้างบัฟเฟอร์วงกลมที่มีประสิทธิภาพใน python (โดยมีเป้าหมายเพื่อหาค่าเฉลี่ยของค่าจำนวนเต็มในบัฟเฟอร์)

นี่เป็นวิธีที่มีประสิทธิภาพในการใช้รายการเพื่อรวบรวมค่าหรือไม่

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

อะไรจะมีประสิทธิภาพมากกว่ากัน (และทำไม)


นี่ไม่ใช่วิธีที่มีประสิทธิภาพในการใช้บัฟเฟอร์แบบวงกลมเนื่องจากการดำเนินการ pop (0) คือ O (n) ในรายการ ป๊อป (0) จะลบองค์ประกอบแรกในรายการและองค์ประกอบทั้งหมดจะต้องเลื่อนไปทางซ้าย ใช้ collection.deque กับแอตทริบิวต์ maxlen แทน deque มีการดำเนินการ O (1) สำหรับผนวกและป๊อป
Vlad Bezden

คำตอบ:


205

ฉันจะใช้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 โดยทีมงานที่มีทักษะอย่างไม่น่าเชื่อซึ่งมีนิสัยชอบไขรหัสชั้นยอด


7
+1 ใช่มันเป็นวิธีรวมแบตเตอรี่ที่ดี การดำเนินการสำหรับบัฟเฟอร์แบบวงกลมคือ O (1) และอย่างที่คุณบอกว่าค่าโสหุ้ยพิเศษอยู่ใน C ดังนั้นควรจะยังค่อนข้างเร็ว
John La Rooy

7
ฉันไม่ชอบโซลูชันนี้เนื่องจากเอกสารไม่รับประกันการเข้าถึงแบบสุ่ม O (1) เมื่อmaxlenมีการกำหนด O (n) เป็นสิ่งที่เข้าใจได้เมื่อdequeสามารถเติบโตเป็นอินฟินิตี้ แต่ถ้าmaxlenได้รับการจัดทำดัชนีองค์ประกอบควรเป็นเวลาคงที่
lvella

1
ฉันเดาว่ามันใช้งานเป็นรายการที่เชื่อมโยงไม่ใช่อาร์เรย์
e-satis

1
ดูเหมือนจะถูกต้องถ้าการกำหนดเวลาในคำตอบของฉันด้านล่างถูกต้อง
djvg

13

การโผล่ออกมาจากส่วนหัวของรายการทำให้รายการทั้งหมดถูกคัดลอกดังนั้นจึงไม่มีประสิทธิภาพ

คุณควรใช้รายการ / อาร์เรย์ที่มีขนาดคงที่และดัชนีซึ่งเคลื่อนผ่านบัฟเฟอร์แทนเมื่อคุณเพิ่ม / ลบรายการ


4
ตกลง. ไม่ว่าสิ่งนั้นจะดูสง่างามหรือไม่สง่างามเพียงใดหรือใช้ภาษาอะไรก็ตาม ในความเป็นจริงยิ่งคุณรบกวนตัวเก็บขยะน้อยลง (หรือตัวจัดการฮีปหรือกลไกการเพจ / การแมปหรืออะไรก็ตามที่ใช้เวทย์มนตร์หน่วยความจำจริง) ยิ่งดี

@RocketSurgeon ไม่ใช่เวทมนตร์ แต่เป็นอาร์เรย์ที่มีการลบองค์ประกอบแรก ดังนั้นสำหรับอาร์เรย์ขนาด n นี่หมายถึงการดำเนินการคัดลอก n-1 ที่นี่ไม่มีเครื่องเก็บขยะหรืออุปกรณ์ที่คล้ายกัน
Christian

3
ฉันเห็นด้วย. การทำเช่นนี้ยังง่ายกว่าที่บางคนคิด เพียงใช้ตัวนับที่เพิ่มขึ้นเรื่อย ๆ และใช้ตัวดำเนินการโมดูโล (% arraylen) เมื่อเข้าถึงรายการ
Andre Blum

idem คุณสามารถตรวจสอบโพสต์ของฉันด้านบนนั่นคือวิธีที่ฉันทำ
MoonCactus

10

จากคำตอบของ 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 อนุญาตให้ใช้ดัชนีเชิงลบอยู่แล้ว แต่ (-1) เช่นจะไม่ส่งคืนค่าที่คาดไว้เมื่อบัฟเฟอร์แบบวงกลมเต็มเนื่องจากการเพิ่ม "สุดท้าย" ของรายการจะสิ้นสุดลงในรายการ
MoonCactus

1
มันใช้งานได้ @MoonCactus ดู 6 ตัวอย่างที่ฉันให้ไว้ด้านบนของคำตอบ ในข้อสุดท้ายคุณจะเห็นว่าc[-1]เป็นองค์ประกอบที่ถูกต้องเสมอ __getitem__มันถูกต้อง
Basj

โอ้ใช่ฉันหมายความว่าฉันล้มเหลวไม่ใช่ของคุณขอโทษ: DI จะทำให้ความคิดเห็นของฉันชัดเจนขึ้น! - โอ้ฉันทำไม่ได้ความคิดเห็นเก่าเกินไป
MoonCactus

วิธีง่ายๆที่ดี ฉันเพิ่มอาร์กิวเมนต์ที่เป็นทางเลือกเพื่ออนุญาตการเริ่มต้นของรายการจากข้อมูลที่มีอยู่มันเป็น pythonpathetic มากกว่าด้วยวิธีนั้น
Orwellophile

9

deque ของ Python ช้า คุณยังสามารถใช้ numpy.roll แทนได้ คุณจะหมุนตัวเลขในอาร์เรย์ numpy ของรูปร่าง (n) หรือ (n, 1) ได้อย่างไร?

ในเกณฑ์มาตรฐานนี้ deque คือ 448ms Numpy.roll คือ 29ms http://scimusing.wordpress.com/2013/10/25/ring-buffers-in-pythonnumpy/


1
แต่numpy.rollส่งคืนสำเนาของอาร์เรย์ใช่ไหม
djvg

3
คำตอบนี้ทำให้เข้าใจผิดมาก - deque ของ Python ดูเหมือนจะค่อนข้างเร็ว แต่การแปลงจากและเป็นอาร์เรย์ numpy จะทำให้มันช้าลงอย่างมากในการวัดประสิทธิภาพที่คุณเชื่อมโยง
xitrium

7

ตกลงกับการใช้คลาส 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

ฉันได้รับTypeError: 'numpy.float64' object is not callableเมื่อพยายามเรียกaveragemethod
scls

ใช่ ... อันที่จริงฉันเดาว่า deque ใช้อาร์เรย์ numpy ภายใน (หลังจากลบ @property แล้วมันก็ใช้ได้ดี)
scls

17
ฉันรับประกันว่า deque ไม่ใช้อาร์เรย์ numpy ภายใน collectionsเป็นส่วนหนึ่งของไลบรารีมาตรฐานnumpyไม่ใช่ การพึ่งพาไลบรารีของบุคคลที่สามจะทำให้ไลบรารีมาตรฐานแย่มาก

6

แม้ว่าจะมีคำตอบที่ดีมากมายอยู่แล้ว แต่ฉันไม่พบการเปรียบเทียบการกำหนดเวลาโดยตรงสำหรับตัวเลือกที่กล่าวถึง ดังนั้นโปรดค้นหาความพยายามที่ต่ำต้อยของฉันในการเปรียบเทียบด้านล่าง

สำหรับวัตถุประสงค์ในการทดสอบเท่านั้นคลาสสามารถสลับระหว่างlistบัฟเฟอร์ฐาน a collections.deque-based บัฟเฟอร์และ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)

4

วิธีการแก้ปัญหาจาก 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())

ทางเลือกการออกแบบที่โดดเด่นในการดำเนินการก็คือว่าเนื่องจากวัตถุเหล่านี้ได้รับการเปลี่ยนแปลงรัฐ nonreversible ที่จุดในช่วงชีวิตของพวกเขาบางส่วนจากที่ไม่เต็มบัฟเฟอร์เต็มบัฟเฟอร์ (และการเปลี่ยนแปลงพฤติกรรมที่จุดนั้น) -I self.__class__สร้างแบบจำลองที่ได้โดยการเปลี่ยน สิ่งนี้ใช้ได้แม้ใน Python 2.2 ตราบใดที่ทั้งสองคลาสมีสล็อตเดียวกัน (เช่นใช้ได้ดีกับคลาสคลาสสิกสองคลาสเช่น RingBuffer และ__Fullในสูตรนี้)

การเปลี่ยนคลาสของอินสแตนซ์อาจเป็นเรื่องแปลกในหลายภาษา แต่ก็เป็นทางเลือกของ Pythonic แทนวิธีอื่น ๆ ในการแสดงการเปลี่ยนแปลงสถานะครั้งใหญ่ครั้งใหญ่ย้อนกลับไม่ได้และไม่ต่อเนื่องซึ่งส่งผลต่อพฤติกรรมอย่างมากเช่นเดียวกับในสูตรนี้ สิ่งที่ดีที่ Python รองรับสำหรับคลาสทุกประเภท

เครดิต: Sébastien Keim


ฉันทำการทดสอบความเร็วของตัวนี้เทียบกับ deque ช้ากว่า deque ประมาณ 7 เท่า
PolyMesh

@PolyMesh สุดยอดคุณควรแจ้งให้ผู้เขียนทราบ!
d8aninja

1
อะไรคือประเด็นของสิ่งนั้น? เป็นเอกสารเก่าที่เผยแพร่แล้ว ประเด็นของความคิดเห็นของฉันคือบอกให้คนอื่นรู้ว่าคำตอบนี้ล้าสมัยและใช้ deque แทน
PolyMesh

@PolyMesh มันอาจจะยังช้ากว่าตอนที่เขาเผยแพร่; คำแนะนำในการติดต่อผู้เขียนอยู่ในบทนำของหนังสือเล่มนี้ ฉันแค่พูดถึงทางเลือกหนึ่งที่เป็นไปได้ นอกจากนี้ "หากความเร็วเท่านั้นเป็นตัวชี้วัดที่ดีที่สุด แต่อาจเป็นเพียงเมตริกที่ดีเท่านั้น"
d8aninja

3

คุณยังสามารถดูสูตร Python ที่ค่อนข้างเก่านี้ได้

นี่คือเวอร์ชันของฉันเองที่มีอาร์เรย์ 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

4
+1 สำหรับการใช้ numpy แต่ -1 สำหรับการไม่ใช้บัฟเฟอร์แบบวงกลม วิธีที่คุณใช้งานคุณจะเปลี่ยนข้อมูลทั้งหมดทุกครั้งที่คุณเพิ่มองค์ประกอบเดียวทำให้เสียO(n)เวลา ในการใช้บัฟเฟอร์แบบวงกลมที่เหมาะสมคุณควรมีทั้งดัชนีและตัวแปรขนาดและคุณต้องจัดการกับเคสอย่างถูกต้องเมื่อข้อมูล 'ล้อมรอบ' ในตอนท้ายของบัฟเฟอร์ เมื่อดึงข้อมูลคุณอาจต้องเชื่อมสองส่วนที่จุดเริ่มต้นและจุดสิ้นสุดของบัฟเฟอร์
Bas Swinckels

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 ของเวลาที่เทียบเท่ากับ dequeue


1
คุณไม่ควร__getitem__มีพลังมากกว่านี้: self._data[(key + self._index + 1) % self._size]?
Mateen Ulhaq

ทำไมคุณถึงต้องการเปลี่ยนโดย +1 ตอนนี้ใช่ดูตัวแปร Basj ด้านล่างสำหรับแนวคิด
MoonCactus

1

ฉันเคยมีปัญหานี้ก่อนที่จะทำการเขียนโปรแกรมแบบอนุกรม ในช่วงเวลากว่าหนึ่งปีที่ผ่านมาฉันไม่พบการใช้งานที่มีประสิทธิภาพเช่นกันดังนั้นฉันจึงเขียนหนึ่งเป็นส่วนขยาย Cและยังมีอยู่ใน pypiภายใต้ใบอนุญาต MIT มันเป็นพื้นฐานสุด ๆ จัดการบัฟเฟอร์ของอักขระที่ลงนาม 8 บิตเท่านั้น แต่มีความยาวที่ยืดหยุ่นดังนั้นคุณสามารถใช้โครงสร้างหรือบางอย่างที่อยู่ด้านบนได้หากคุณต้องการอย่างอื่นที่ไม่ใช่ตัวอักษร ฉันเห็นตอนนี้ด้วยการค้นหาโดย Google ว่ามีหลายตัวเลือกในทุกวันนี้ดังนั้นคุณอาจต้องการดูสิ่งเหล่านี้ด้วย


1

คุณตอบไม่ถูก บัฟเฟอร์แบบวงกลมหลักมีสองราคา ( https://en.wikipedia.org/wiki/Circular_buffer )

  1. ความยาวของบัฟเฟอร์ถูกกำหนดไว้
  2. เข้าก่อนออกก่อน;
  3. เมื่อคุณเพิ่มหรือลบรายการรายการอื่น ๆ ไม่ควรย้ายตำแหน่ง

รหัสของคุณด้านล่าง:

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 ฉันคิดว่ามีประสิทธิภาพมากที่สุด

อย่างไรก็ตามบัฟเฟอร์วงกลมสามารถกระตุ้นประสิทธิภาพของการดำเนินการเพื่อเพิ่มรายการได้


1

จาก 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


0

คำถามเดิมคือบัฟเฟอร์แบบวงกลม " มีประสิทธิภาพ " ตามประสิทธิภาพนี้ที่ถามหาคำตอบจาก aaronasterling ดูเหมือนจะถูกต้องแน่นอน การใช้คลาสเฉพาะที่ตั้งโปรแกรมไว้ใน Python และเปรียบเทียบการประมวลผลเวลากับคอลเลกชันที่แสดงการเร่งความเร็ว x5.2 เท่าด้วย deque! นี่คือรหัสที่ง่ายมากในการทดสอบสิ่งนี้:

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

ในการแปลง deque เป็นรายการเพียงใช้:

my_list = [v for v in my_deque]

จากนั้นคุณจะได้รับ O (1) การเข้าถึงโดยสุ่มไปยังรายการ deque แน่นอนว่านี่จะมีค่าก็ต่อเมื่อคุณต้องทำการสุ่มเข้าถึง deque หลาย ๆ ครั้งหลังจากตั้งค่าไปแล้วหนึ่งครั้ง


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:])  

0

คุณสามารถตรวจสอบบัฟเฟอร์แบบวงกลมนี้ตามอาร์เรย์ numpy ขนาดที่กำหนดไว้ล่วงหน้า แนวคิดก็คือคุณสร้างบัฟเฟอร์ (จัดสรรหน่วยความจำสำหรับอาร์เรย์ numpy) แล้วต่อท้าย การแทรกข้อมูลและการดึงข้อมูลทำได้รวดเร็วมาก ฉันได้สร้างโมดูลนี้เพื่อจุดประสงค์ที่คล้ายกันตามที่คุณต้องการ ในกรณีของฉันฉันมีอุปกรณ์ที่สร้างข้อมูลจำนวนเต็ม ฉันอ่านข้อมูลและวางไว้ในบัฟเฟอร์วงกลมสำหรับการวิเคราะห์และประมวลผลในอนาคต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.