tags:

views:

119

answers:

5

So I have an array that holds several numbers. As my script runs, more and more numbers are appended to this array. However, I am not interested in all the numbers but just want to keep track of the last 5 numbers.

Currently, I just store all the numbers in the array. However, this array gets really big and it's full of unnecessary information.

I have thought about making a function that when it adds an element to the array, also removes the last element if the array already contains 5 numbers.

I also thought about making a new class to create a data structure that does what I want. However, I only need to reference this array occasionally and is only a small part of the script. So I think it is overkill if I create a whole new class to do this.

What is the best way to do this?

+10  A: 

Try using a deque: http://docs.python.org/library/collections.html#deque-objects

"If maxlen is not specified or is None, deques may grow to an arbitrary length. Otherwise, the deque is bounded to the specified maximum length. Once a bounded length deque is full, when new items are added, a corresponding number of items are discarded from the opposite end. Bounded length deques provide functionality similar to the tail filter in Unix. They are also useful for tracking transactions and other pools of data where only the most recent activity is of interest."

shookster
awesome this is perfect. thanks.
user
+1, but just for reference this requires python 2.6 or higher.
Michael Anderson
+4  A: 

The class can be pretty trivial:

class ListOfFive:
  def __init__(self):
    self.data = []
  def add(self,val):
    if len(self.data)==5:
      self.data=self.data[1:]+[val]
    else:
      self.data+=[val]

l = ListOfFive()
for i in range(1,10):
  l.add(i)
  print l.data

Output is:

[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[2, 3, 4, 5, 6]
[3, 4, 5, 6, 7]
[4, 5, 6, 7, 8]
[5, 6, 7, 8, 9]
Michael Anderson
The batteries are already included, sir ;)
msw
@msw, Alas, not in python 2.5.1 which is what I'm stuck with...The limited size deque seems to only be in python 2.6 and higher. So if you're using 2.6+ go with limited length deques, otherwise you can use something like my solution
Michael Anderson
+1 fair nuff, and well said
msw
Copying lots of memory there.
FogleBird
@FogleBird, Yep. And if it was my project I'd optimise that when I knew it was the bottleneck. For SO I'd rather present clear code than less clear optimised code. However I'm happy to incorporate any suggestions you may have that increase efficiency and don't muddy the code.
Michael Anderson
For Python 2.4 or 2.5 a more efficient way would be to subclass deque to implement maxlen yourself. Overide push to check the length and if it is >= maxlen then popleft().
Dave Kirby
+4  A: 

I fully agree with the idea of using Python's limited-length deque if it's available, and if not, Michael Anderson's simple solution is quite adequate. (I upvoted both) But I just wanted to mention the third option of a ring buffer, which is often used for this kind of task when low memory footprint and high execution speed are important. (In other words, in situations when you probably wouldn't be using Python :-p) For example, the Linux kernel uses this structure to store log messages generated during the boot process, before the system logger starts.

A Python implementation could look like this:

class RingBuffer(object):
    def __init__(self, n):
        self._buf = [None] * n
        self._index = 0
        self._valid = 0
    def add(self, obj):
        n = len(self._buf)
        self._buf[self._index] = obj
        self._index += 1
        if self._index == n
            self._index = 0
        if self._valid < n:
            self._valid += 1
    def __len__(self):
        return self._valid
    # could include other methods for accessing or modifying the contents

Basically what it does is preallocate an array (in Python, a list) of the desired length and fill it with dummy values. The buffer also contains an "index" which points to the next spot in the list that should be filled with a value. Each time a value is added, it's stored in that spot and the index is incremented. When the index reaches the length of the array, it's reset back to zero. Here's an example (I'm using 0 instead of None for the dummy value just because it's quicker to type):

[0,0,0,0,0]
 ^
# add 1
[1,0,0,0,0]
   ^
# add 2
[1,2,0,0,0]
     ^
# add 3
[1,2,3,0,0]
       ^
# add 4
[1,2,3,4,0]
         ^
# add 5
[1,2,3,4,5]
 ^
# add 6
[6,2,3,4,5]
   ^
# add 7
[6,7,3,4,5]
     ^

and so on.

David Zaslavsky
Note that to get the last 5 entries in order you can do something like: def get_all(self): return self._buf[self._index:self._valid]+self._buf[:self._index]
Michael Anderson
Why compute n every time add is called? self._buf never changes length, just add `self._buflen = n` in `__init__`. To increment the index, simpler to use `self._index = (self._index + 1) % n`. And I should think that instead of incrementing self._valid if < n, just set it equal to self._index. I do wonder though, whether you are better off just seeding the buffer with 0's and forgetting about self._valid entirely. self._valid only be < the buffer size for the first 'n' calls to add, after which it tops out at 'n' and never changes. From then on, you're just managing a non-varying value.
Paul McGuire
@Paul: The code I wrote is a weird mix of optimization and inefficiency ;-) but here's my reasoning: I compute `n` in each call to `add` because it's used multiple times and storing it in a local variable saves an attribute lookup. The `%` operator tends to be slow, but resetting to 0 with an `if` statement is faster. You probably have a point about setting `self._valid=self._index`. And if you knew you'd never have to access the buffer before it fills up, `_valid` could certainly be omitted entirely.
David Zaslavsky
A: 

From your description, I would add the following type of statement just after the code that extends your list:

mylist = mylist[-5:]

It will then only ever be a maximum of 5 values in length

Here is a quick example:

>>> mylist = []
>>> i = 1
>>> while i<6:
    print ("\n Pre addition: %r" % mylist)
    mylist += range(i)
    print ("     Addition: %r" % mylist)
    mylist = mylist[-5:]
    print ("      Chopped: %r" % mylist)
    i += 1



Pre addition: []
    Addition: [0]
     Chopped: [0]

Pre addition: [0]
    Addition: [0, 0, 1]
     Chopped: [0, 0, 1]

Pre addition: [0, 0, 1]
    Addition: [0, 0, 1, 0, 1, 2]
     Chopped: [0, 1, 0, 1, 2]

Pre addition: [0, 1, 0, 1, 2]
    Addition: [0, 1, 0, 1, 2, 0, 1, 2, 3]
     Chopped: [2, 0, 1, 2, 3]

Pre addition: [2, 0, 1, 2, 3]
    Addition: [2, 0, 1, 2, 3, 0, 1, 2, 3, 4]
     Chopped: [0, 1, 2, 3, 4]
>>> 
Paddy3118
+1  A: 

Another neat ring buffer implementation can be found in the ActiveState Recipes - your ring buffer object starts out as an instance of RingBuffer while filling up at first, and then your instance changes its class to RingBufferFull, an optimized full implementation. It always makes me smile.

class RingBuffer:
    def __init__(self,size_max):
        self.max = size_max
        self.data = []
    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
            self.__class__ = RingBufferFull
    def get(self):
        """ return a list of elements from the oldest to the newest"""
        return self.data


class RingBufferFull:
    def __init__(self,n):
        raise "you should use RingBuffer"
    def append(self,x):     
        self.data[self.cur]=x
        self.cur=(self.cur+1) % self.max
    def get(self):
        return self.data[self.cur:]+self.data[:self.cur]
Kiv
Cool :-) That'd probably be a better way to do it in Python - when I wrote mine I was thinking more along the lines of translating from C (where there are of course no classes). +1
David Zaslavsky