flatten(), [was Re: map/filter/reduce/lambda opinions and background unscientific mini-survey]
Ron Adam
rrr at ronadam.com
Tue Jul 5 17:39:45 EDT 2005
Tom Anderson wrote:
> The trouble with these is that they make a lot of temporary lists -
> George's version does it with the recursive calls to flatten, and Ron's
> with the slicing and concatenating. How about a version which never
> makes new lists, only appends the base list? We can use recursion to
> root through the lists ...
Ok... How about a non-recursive flatten in place? ;-)
def flatten(seq):
i = 0
while i!=len(seq):
while isinstance(seq[i],list):
seq.__setslice__(i,i+1,seq[i])
i+=1
return seq
seq = [[1,2],[3],[],[4,[5,6]]]
print flatten(seq)
I think I'll be using the __setslice__ method more often.
And the test:
#--------------------------------
# Georges recursive flatten
init_a = """
def flatten(seq):
return reduce(_accum, seq, [])
def _accum(seq, x):
if isinstance(x,list):
seq.extend(flatten(x))
else:
seq.append(x)
return seq
seq = [[1,2],[3],[],[4,[5,6]]]
"""
# Ron's non-recursive
init_b = """
def flatten(seq):
s = []
while seq:
while isinstance(seq[0],list):
seq = seq[0]+seq[1:]
s.append(seq.pop(0))
return s
seq = [[1,2],[3],[],[4,[5,6]]]
"""
# Tom's recursive, no list copies made
init_c = """
def isiterable(x):
return hasattr(x, "__iter__") # close enough for government work
def visit(fn, x): # perhaps better called applytoall
if (isiterable(x)):
for y in x: visit(fn, y)
else:
fn(x)
def flatten(seq):
a = []
def appendtoa(x):
a.append(x)
visit(appendtoa, seq)
return a
seq = [[1,2],[3],[],[4,[5,6]]]
"""
# Devan' smallest recursive
init_d = """
def flatten(iterable):
if not hasattr(iterable, '__iter__'):
return [iterable]
return sum([flatten(element) for element in iterable],[])
seq = [[1,2],[3],[],[4,[5,6]]]
"""
# Ron's non-recursive flatten in place! Much faster too!
init_e = """
def flatten(seq):
i = 0
while i!=len(seq):
while isinstance(seq[i],list):
seq.__setslice__(i,i+1,seq[i])
i+=1
return seq
seq = [[1,2],[3],[],[4,[5,6]]]
"""
import timeit
t = timeit.Timer("flatten(seq)",init_a)
print 'recursive flatten:',t.timeit()
import timeit
t = timeit.Timer("flatten(seq)",init_b)
print 'flatten in place-non recursive:',t.timeit()
import timeit
t = timeit.Timer("flatten(seq)",init_c)
print 'recursive-no copies:',t.timeit()
import timeit
t = timeit.Timer("flatten(seq)",init_d)
print 'smallest recursive:',t.timeit()
import timeit
t = timeit.Timer("flatten(seq)",init_e)
print 'non-recursive flatten in place without copies:',t.timeit()
#--------------------------------------------
The results on Python 2.3.5: (maybe someone can try it on 2.4)
recursive flatten: 23.6332723852
flatten in place-non recursive: 22.1817641628
recursive-no copies: 30.909762833
smallest recursive: 35.2678756658
non-recursive flatten in place without copies: 7.8551944451
A 300% improvement!!!
This shows the value of avoiding copies, recursion, and extra function
calls.
Cheers,
Ron Adam
More information about the Python-list
mailing list