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]

      

+3


source to share


6 answers


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)

      

+7


source


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()))

      

+4


source


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()

.

0


source


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.

0


source


It's not pretty, but you can do something among these lines:

nay = []
yay = [foo for foo in foos if condition or nay.append(foo)]

      

This makes use of a short circuit on the operator or

.

0


source


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!

0


source







All Articles