Using break in a list comprehension

Question:

How can I break a list comprehension based on a condition, for instance when the number 412 is found?

Code:

numbers = [951, 402, 984, 651, 360, 69, 408, 319, 601, 485, 980, 507, 725, 547, 544,
           615, 83, 165, 141, 501, 263, 617, 865, 575, 219, 390, 984, 592, 236, 105, 942, 941,
           386, 462, 47, 418, 907, 344, 236, 375, 823, 566, 597, 978, 328, 615, 953, 345, 399,
           162, 758, 219, 918, 237, 412, 566, 826, 248, 866, 950, 626, 949, 687, 217, 815, 67,
           104, 58, 512, 24, 892, 894, 767, 553, 81, 379, 843, 831, 445, 742, 717, 958, 609, 842,
           451, 688, 753, 854, 685, 93, 857, 440, 380, 126, 721, 328, 753, 470, 743, 527]

even = [n for n in numbers if 0 == n % 2]

So functionally, it would be something you can infer this is supposed to do:

even = [n for n in numbers if 0 == n % 2 and break if n == 412]

I really prefer:

  • a one-liner
  • no other fancy libraries like itertools, “pure python” if possible (read: the solution should not use any import statement or similar)
Asked By: Flavius

||

Answers:

If 412 will definitely be in the list you could use this:

even = [n for n in numbers[:numbers.index(412)] if not n % 2]

If you want to include 412 in the result just use numbers[:numbers.index(412)+1] for the slice.

Note that because of the slice this will be less efficient (at least memory-wise) than an itertools or for loop solution.

Answered By: Andrew Clark

You can use generator expressions together with itertools.takewhile():

even_numbers = (n for n in numbers if not n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))

Edit: I just noticed the requirement not to use any imports. Well, I leave this answer here anyway.

Answered By: Sven Marnach

Use a function to raise StopIteration and list to catch it:

>>> def end_of_loop():
...     raise StopIteration
... 
>>> even = list(end_of_loop() if n == 412 else n for n in numbers if 0 == n % 2)
>>> print(even)
[402, 984, 360, 408, 980, 544, 390, 984, 592, 236, 942, 386, 462, 418, 344, 236, 566, 978, 328, 162, 758, 918]

For those complaining it is not a one-liner:

even = list(next(iter(())) if n == 412 else n for n in numbers if 0 == n % 2)

For those complaining it is hackish and should not be used in production code: Well, you’re right. Definitely.

Answered By: Reinstate Monica

The syntax for list displays (including list comprehensions) is here: http://docs.python.org/reference/expressions.html#list-displays

As you can see, there is no special while or until syntax. The closest you can get is:

even_numbers = (n for n in numbers if 0 == n % 2)
list(itertools.takewhile(lambda x: x != 412, even_numbers))

(Code taken from Sven Marnach’s answer, posted while I was typing this).

Answered By: Marcin
even = [n for n in numbers[:None if 412 not in numbers else numbers.index(412)] if not n % 2] 

Just took F.J.’s code above and added a ternary to check if 412 is in the list. Still a ‘one liner’ and will work even if 412 is not in the list.

I know this is a VERY OLD post, however since OP asked about using break inside a list-comprehension and I was also looking for something similar, I thought I would post my findings here for future reference.

While investigating break, I came across little known feature of iter as iter(callable, sentinel) which return an iterator that “breaks” iteration once callable function value is equal to sentinel value.

>>> help(iter)
Help on built-in function iter in module __builtin__:

iter(...)
    iter(collection) -> iterator
    iter(callable, sentinel) -> iterator

    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.

Tricky part here is defining a function that would fit given problem. In this case first we need to convert given list of numbers to an iterator using x = iter(numbers) which feeds as external variable into lambda function.

Next, our callable function is just a call to the iterator to spit out next value. The iterator then compares with our sentinel value (412 in this case) and “breaks” once that value is reached.

print [i for i in iter(lambda x=iter(numbers): next(x),412) if i %2 == 0]

>>> 
[402, 984, 360, 408, 980, 544, 390, 984, 592, 236, 942, 386, 462, 418,  
 344, 236, 566, 978, 328, 162, 758, 918]
Answered By: Anil_M

another sneaky one-line solution to solve breaking in list comprehension, with the help of end condition.

without using numbers.index(412), maybe a little bit faster?

even = [n for end in [[]] for n in numbers
        if (False if end or n != 412 else end.append(42))
        or not end and not n % 2]

Note: This is a bad idea. just for fun : )

as @WolframH said:

For those complaining it is hackish and should not be used in production code: Well, you’re right. Definitely.

Answered By: recnac

Considering the generator solution is outdated I came up with the following:

even = [n for n in next((numbers[:i] for i, n in enumerate(numbers) if n == 412)) if not n % 2]

Then I went back and saw Andrew Clark’s answer which is the same but much better

even = [n for n in numbers[:numbers.index(412)] if not n % 2]

Regardless the best part about the slicing solution is you can choose to include or exclude a number of elements on either side of the ending element for example to get 412 and the number after:

even = [n for n in numbers[:numbers.index(412)+2] if not n % 2]
Answered By: Alex Cooper

Once I met a similar question on SO, the answer was:

next((x for x in [1, 2, 3, 4] if x % 2 == 0), [])

The last [] needs as default to prevent the StopIteration error if not found

or

any(print(x) if x < 2 else True for x in range(5))

print to prove, he returns None (logically False).

Answered By: Давид Шико
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.