Here is a problem: given a sequence and an item in that sequence, find the item which follows.
For those who code in C-like languages, the first attempt might looks like this:
def find_item_after(sequence, item): items_count = len(sequence) for index in range(items_count): if sequence[index] == item and index < items_count - 1: return sequence[index] return None
The problem with this solution is the ugliness of the code, and the inefficiency of indexing.
More Pythonic Solution
def find_item_after(sequence, item): for item_here, item_after in zip(sequence, sequence[1:]): if item_here == item: return item_after return None
This solution improves in readability, but at the expense of performance. First, the expression
sequence[1:] creates a new sequence from the current one. Then, the
zip function creates yet another sequence that are twice the size of the original requence. Note that in Python 3, zip will return a generator object instead of a sequence, which will help in term of efficiency.
Pythonic and Efficient
Until we use Python 3, we are better off using a generator version of
izip. We can also avoid the memory hogging aspect of
from itertools import izip, islice def find_item_after(sequence, item): for item_here, item_after in izip(sequence, islice(sequence, 1, None)): if item_here == item: return item_after return None
This solution is both Pythonic and efficient. However, we can still do better.
def find_item_after(sequence, item): iterable = iter(sequence) for item_here in iterable: if item_here == item: return next(iterable, None) return None
This solution does not use any external library, nor does it create extra copy of the sequence. The
next function takes a second parameter, the return value in case that we are at the end of the iterable, when
next will generate a
What about Find Before?
We can adapt the solution which use
iter for this purpose:
def find_item_before(sequence, item): iterable = iter(sequence) item_before = None for item_here in iterable: if item_here == item: return item_before item_before = item_here return None
However, the above is too clunky and not efficient: we have to assign a new value to
item_before every time we go through the loop. What about the solution which uses
from itertools import izip, islice def find_item_after(sequence, item): for item_before, item_here in izip(islice(sequence, 1, None), sequence): if item_here == item: return item_before return None
Note that in this solution, we start searching in the sequence starting with the second item (at index 1) and eliminate the first item from the search. If the caller wants an item before the first item, the loop will complete without any result and we return
This problem was originally an interview question, but it does have some practical application