Here's a way to do this that takes advantage of the speed of a list comprehension to make one list, while incrementally appending the rejects onto another list.
blist = []
alist = [x for x in data if x[0]==0 or blist.append(x)]
This takes advantage of Python's greedily evaluating the or
, so for any x
that passes the alist
criterion (x[0]==0
) Python won't bother evaluating the second disjunct (blist.append(x)
). This is a case where it's fairly intuitive to read Python's "or" as meaning "or else do this second thing and use its output instead" (which is technically what it always means).
This also takes advantage of the fact that list.append
always returns a false-like None
value, so that in the case where some x
fails the alist
criterion and thus gets appended to blist
instead, the or
still evaluates as false, so that rejected x
still will not be included in the alist
comprehension.
Here are timeit
results (on my reasonably powerful desktop), each dividing a list of a million random numbers ranging 0-1 into a small
list of those less than 0.1 and a large
list of those larger, each trial repeated 10 times.
0.80 seconds: Using append to create both (suggested by @Ignacio)
0.68 seconds: Separate list comprehensions (suggested by @Max)
0.68 seconds: Using append to create large, comprehend to create small (inverse of what I would suggest!)
0.52 seconds: Using append to create small, comprehend to create large (suggested by me)
These results confirm what you would probably expect: primarily, list comprehension builds big lists a lot faster than append
does, and, secondarily, 1 traversal is faster than 2. If you can predict which list will be longer, better to build that large list by comprehension, while appending just a few rejects into the smaller list.
For anyone who wants to try this themselves, here's the timing code.
import random, timeit
L = [random.random() for i in range(1000000)]
def comprehend_both(): # suggested by @max
small = [n for n in L if n <= 0.1]
large = [n for n in L if n > 0.1]
def append_both(): # suggested by @ignacio
lists = small, large = [[],[]]
for n in L: lists[n > 0.1].append(n) # exploits False==0, True==1
def append_small_comprehend_large(): # suggested by @JustinFisher
small = []
large = [n for n in L if n > 0.1 or small.append(n)]
def comprehend_small_append_large(): # worse since appending more is slower
large = []
small = [n for n in L if n <= 0.1 or large.append(n)]
print( "C/C:", timeit.timeit("comprehend_both()",globals=globals(),number=10)) # 0.68 seconds
print( "A/A:", timeit.timeit("append_both()",globals=globals(),number=10)) # 0.80 seconds
print( "A/C:", timeit.timeit("append_small_comprehend_large()",globals=globals(),number=10)) # returns 0.52
print( "C/A:", timeit.timeit("comprehend_small_append_large()",globals=globals(),number=10)) # returns 0.68