Pop a list from beginning ? and memory saving...

Jeff Epler jepler at unpythonic.net
Thu Jun 20 15:50:36 EDT 2002


I seem to recall a discussion, probably a few years ago by now, for a
list implementation that was efficient to grow or shrink from either
end.  I don't recall why it wasn't adopted.  I also can't find the
discussion. :(

I suspect that having unallocated space at both the front and the back
of the list, growing according to the same exponential curve when the
end is reached, would give this kind of behavior.  

The following isn't finished, but append, insert and pop seem to work
properly, and on my machine already beat the builtin list for at-front
operations at 10000 items (test 3/4) but lag in at-end operations (test
1/2):
    10000   test1   test2   test3   test4
    list    0.042   0.075   1.23    1.28
    delist  0.051   0.23    0.22    0.47

    100000  test1   test2   test3   test4
    list    0.48    0.92    151.2   127.6
    delist  0.56    2.46    5.45    9.31
(I didn't let a 1M-item test finish)

Jeff


def roundupsize(n):
    nbits = 0
    n2 = n >> 5
    while 1:
	n2 >>= 3
	nbits += 3
	if not n2: break
    return ((n>>nbits)+1)<<nbits

class delist(list):
    def __init__(self, data=[]):
	super(delist, self).__init__(data)
	self.start = 0
	self.__super = super(delist, self)

    def __growstart(self):
	l = len(self)
	nl = roundupsize(l)
	self[0:0] = [None] * (nl-l)
	self.start = self.start + nl-l

    def __len__(self):
	return self.__super.__len__() - self.start

    def pop(self, idx=None):
	if idx is None:
	    return self.__super.pop()
	if idx == 0:
	    ret = self[idx]
	    start = self.start
	    self[start] = None
	    self.start = start + 1
	    return ret
	else:
	    if idx > 0: idx = idx + len(self)
	    return self.__super.pop(idx + self.start)

    def __getitem__(self, idx):
	self.__super.__getitem__(idx + self.start)

    def insert(self, idx, item):
	if idx == 0:
	    if not self.start:
		self.__growstart()
	    self.start = self.start - 1
	    self[self.start] = item
	    return
	self.__super.insert(idx+self.start, item)

    def __repr__(self):
	return "<dlist +%d %s>" % (self.start, list(self[self.start:]))

def test():
    import time

    def test1(x):
	# Test speed of append
	assert not x
	for i in r:
	    x.append(i)
	assert len(x) == len(r)
	return x

    def test2(x):
	# Test speed of pop-from-end
	assert len(x) == len(r)
	for i in r:
	    x.pop()
	assert not x

    def test3(x):
	# test speed of insert-at-start
	assert not x
	for i in r:
	    x.insert(0, i)
#	    print x
	assert len(x) == len(r)
	return x

    def test4(x):
	# test speed of pop-from-start
	assert len(x) == len(r)
	for i in r:
	    x.pop(0)
	assert not x

    def run(f, arg):
	t0 = time.time()
	ret = f(arg)
	dt = time.time() - t0
	print "%s/%s/%d: %f" % (f.__name__, type(arg), len(r), dt)
	return ret

    for rr in (10000, 100000, 1000000):
	r = range(rr)
	for ll in list(), delist():
	    ll = run(test1, ll)
	    run(test2, ll)
	for ll in list(), delist():
	    ll = run(test3, ll)
	    run(test4, ll)

test()





More information about the Python-list mailing list