views:

1412

answers:

5

Hello Stack Overflow Community,

I have a list where I want to replace values with None where condition() returns True.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

For example, if condition checks bool(item%2) should return:

[None, 1, None, 3, None, 5, None, 7, None, 9, None]

What is the most efficient way to do this?

+1  A: 
>>> L = range (11)
>>> [ x if x%2 == 1 else None for x in L ]
[None, 1, None, 3, None, 5, None, 7, None, 9, None]
eduffy
+3  A: 

The most efficient:

items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for item, index in enumerate(items):
    if not (item % 2):
        items[index] = None

The easiest to read:

new_items = [x if x % 2 else None for x in items]
John Millikin
is that the most efficient? doesn't enumerate have to create an iterator and form a tuple, adding overhead? are lists in python arraylists, giving you constant time access?
geowa4
I think, and I might be wrong, that he meant for a copy of the list to be returned instead of modifying the original in place. But still, +1 for offering the efficient solution when in-place modification is allowed.
A. Levy
If I wanted to modify the original in place, wouldn't it also possible to use imap from itertools?
ak
@geowa4: Python "lists" are actually arrays. `enumerate()` will ad a small overhead, but if that's unacceptable the index can be tracked manually. @ak: I don't understand the question. `imap()` is not an in-place operation.
John Millikin
@John Millikin: I was just thinking, what if I had a generator that yields the values from range(11) instead of a list. Would it be possible to replace values in the generator?
ak
Generators don't have values to replace; when called, their `next()` method returns values until eventually it doesn't. This is a useful API for generic iteration, but it prevents some performance optimizations.
John Millikin
+11  A: 
ls = [x if (condition) else None for x in ls]
Laurence Gonsalves
+2  A: 

Riffing on a side question asked by the OP in a comment, i.e.:

what if I had a generator that yields the values from range(11) instead of a list. Would it be possible to replace values in the generator?

Sure, it's trivially easy...:

def replaceiniter(it, predicate, replacement=None):
  for item in it:
    if predicate(item): yield replacement
    else: yield item

Just pass any iterable (including the result of calling a generator) as the first arg, the predicate to decide if a value must be replaced as the second arg, and let 'er rip.

For example:

>>> list(replaceiniter(xrange(11), lambda x: x%2))
[0, None, 2, None, 4, None, 6, None, 8, None, 10]
Alex Martelli
+1 hehe... i want to learn how to write this "one" line nifty python solution... hint pls
gath
@gath, I don't understand your question -- comments are pretty limiting so you should open a new question so you can expand and clarify what is it you're looking for...
Alex Martelli
A: 

Here's another way:

>>> L = range (11)
>>> map(lambda x: x if x%2 else None, L)
[None, 1, None, 3, None, 5, None, 7, None, 9, None]
balpha
+1 and how do guys learn this one line nifty python code...hints
gath
@gath: Don't aspire to write one-liners for every purpose. Sometimes, they increase readability or performance, but often they don't. As to hints: Get to know the tools that Python ofters, especially list (and for Python 3 also dict) comprehensions, the ternary operator, anonymous (lambda) functions, and functions like map, zip, filter, reduce, etc.
balpha