'Advanced' list comprehension? query

John Krukoff jkrukoff at ltgc.com
Wed Aug 8 19:34:47 CEST 2007


pyscottishguy at hotmail.com wrote:
> Hi,
> 
> I'm playing around with list comprehension, and I'm trying to find the
> most aesthetic way to do the following:
> 
> I have two lists:
> 
> noShowList = ['one', 'two', 'three']
> 
> myList = ['item one', 'item four', 'three item']
> 
> I want to show all the items from 'myList' that do not contain any of
> the strings in 'noShowList'.
> 
> i.e. 'item four'
> 
> I can do it like this:
> 
> def inItem(noShowList, listitem):
>     return [x for x in noShowList if x in listitem]
> 
> print [x for x in myList if not inItem(noShowList, x)]
> 
> and I can do it (horribly) with:
> 
> print [x for x in myList if not (lambda y, z:[i for i in y if i in z])
> (noShowList, x)]
> 
> I can also print out the items that DO contain the 'noShowList'
> strings with:
> 
> print [x for x in myList for y in noShowList if y in x]
> 
> but I can't get the 'not' bit to work in the above line.
> 
> Any ideas?
> Thanks!
> 
> --
> http://mail.python.org/mailman/listinfo/python-list

So, conceptually speaking, you're dealing with two loops here, one over the
items to filter, and one over the items to check for substring matches. If
you want to do that with list comprehensions, I'd make it obvious that
there's two of them:

>>> [ listItem for listItem in myList if not [ noShow for noShow in
noShowList if noShow in listItem ] ]
['item four']

This is a pretty good place for the functional programming tools though,
specifically "filter",
http://docs.python.org/tut/node7.html#SECTION007130000000000000000
, which gives a solution that looks like this:

>>> filter( lambda listItem : not [ noShow for noShow in noShowList if
noShow in listItem ], myList )
['item four']

or using purely functional tools, like this:

>>> filter( lambda listItem : not sum( map( lambda noShow: noShow in
listItem, noShowList ) ), myList )
['item four']

All these solutions have the problem that they're still less efficient than
the unwrapped for loop, like so:

>>> aFiltered = []
>>> for listItem in myList:
...     for noShow in noShowList:
...         if noShow in listItem:
...             break
...     else:
...         aFiltered.append( listItem )
...     
>>> aFiltered
['item four']

This is due to the list comprehensions testing all the possiblities, instead
of giving up on the first one found. You can jam that early break into the
functional approach using itertools, but it starts to look really ugly on
one line (requires 2.5 for if expression):

>>> list( itertools.ifilter( lambda listItem : True if len( list(
itertools.takewhile( lambda test : not test, itertools.imap( lambda noShow:
noShow in listItem, noShowList ) ) ) ) == len( noShowList) else False,
myList ) )
['item four']

Which can be made to look much better by breaking the 'noShow in listItem'
test out into a separate function, and does have the advantage that by using
itertools.ifilter this is a lazy approach. There's got to be a better way to
do the test to see if takewhile bailed early than using len, though.

---------
John Krukoff
helot at comcast.net




More information about the Python-list mailing list