Is there a way to capture list comprehension misses?
Based on a simple list comprehension:
yay = [ i for i in a if a[i] ]
nay = [ i for i in a if not a[i] ]
I'm wondering if there is a way to immediately assign values to yay
and nay
(i.e. hits and misses on conditional)?
Something like that
( yay , nay ) = ...
I was wondering about this for readability and speed (I was a bit surprised to see that two list contexts are about 5% faster than one for loop that is added to any list)
update:
The original example was to grab a list of "true" and "false" significant keys into a dict ...
a = {i: i >= 50 for i in range(100)}
yay = [k for k, v in a.items() if v]
nay = [k for k, v in a.items() if not v]
source to share
The usual solution here is not to depend on the idea of using a list. Just use a loop for
:
yay, nay = [], []
for i in a:
if somecondition(i):
yay.append(i)
else:
nay.append(i)
If you do that much, just move the code into a function:
def yesno(seq, cond):
yay, nay = [], []
for i in seq:
if cond(i):
yay.append(i)
else:
nay.append(i)
return yay, nay
yay, nay = yesno(a, lambda x: a[x])
The comments say this is slower than list comprehension. Passing state as a lambda will inevitably have a lot of success, and I don't think you can do much about it, but some of the performance results probably come from a method lookup append
and this could be improved:
def yesno(seq, cond):
yay, nay = [], []
yes, no = yay.append, nay.append
for i in seq:
if cond(i):
yes(i)
else:
no(i)
return yay, nay
I don't know if it could make a big difference, but it might be an interesting time.
In the comments, @martineau suggests using a generator and consuming it with any()
. I'll include this here, but I would any
replace the itertools recipe for using an iterator:
def consume(iterator, n):
"Advance the iterator n-steps ahead. If n is none, consume entirely."
# Use functions that consume iterators at C speed.
if n is None:
# feed the entire iterator into a zero-length deque
collections.deque(iterator, maxlen=0)
else:
# advance to the empty slice starting at position n
next(islice(iterator, n, n), None)
and then you can write:
yay, nay = [], []
consume((yay if a[i] else nay).append(i) for i in a)
source to share
I would say your way to make it more readable and should be suggested, but anyway if you are looking for an alternative you can look forward to itertools solution
>>> from itertools import compress, imap
>>> from operator import not_
>>> yay, nay = compress(a,a.values()), compress(a, imap(not_,a.values()))
source to share
You can do it something like this:
yay, nay = zip(*[(k, None) if v else (None, k) for k, v in a.items()])
yay, nay = filter(None, yay), filter(None, nay)
As for if it would be faster ... maybe for huge lists. If not, it probably doesn't matter.
Naturally, if it None
is a value in your lists, you will need to change it to another watch and perform an identity check with filter()
.
source to share
You might be able to use a dict comprehension, but I'm pretty sure you can't * use a list comprehension to do what you ask. Assuming the data is sorted or can be sorted ** I would probably use itertools.groupby
.
results = itertools.groupby(sorted_a, bool)
* Qualification: OK, Lattyware's answer shows you can, but also generates a tuple with a value None
for each iterable member. IMO that's a lot of waste. While I admit I didn't even think about it, I'm not ashamed that I didn't.
** Sorted: It needs to be sorted by the same key that it has grouped.
source to share
EDIT
Ok, I wrote a solution that was the same as one of Duncan's. So I delete what I wrote and I resolve what I think is the best solution by mixing one Duncan solution and martineau's suggestion (using any () seems to me more preferable to use from list () or list comprehension as what I wrote ; very good idea any () , which is better than the complexity of import to consume () IMO)
def disting(L):
dust,right = [],[]
dustapp = dust.append
rightapp = right.append
any(rightapp(x) if x else dustapp(x) for x in L)
return right,dust
for seq in ((10,None,'a',0,None,45,'qada',False,True,0,456),
[False,0,None,104,True,str,'',88,'AA',__name__]):
yay,nay = disting(seq)
print 'seq == %r\nyay == %r\nnay == %r' % (seq,yay,nay)
print '---------------------------------------'
result
seq == (10, None, 'a', 0, None, 45, 'qada', False, True, 0, 456)
yay == [10, 'a', 45, 'qada', True, 456]
nay == [None, 0, None, False, 0]
---------------------------------------
seq == [False, 0, None, 104, True, <type 'str'>, '', 88, 'AA', '__main__']
yay == [104, True, <type 'str'>, 88, 'AA', '__main__']
nay == [False, 0, None, '']
---------------------------------------
By the way, using any () works because it rightapp(x)
also dustapp(x)
returns None. If True or the equivalent True is returned, the iteration inside any () will stop!
source to share