Shaken_not_stirred.
Shaken_not_stirred.

Reputation: 516

Python: list comprehension to produce consecutive sublists of even numbers

I'm trying to practice Python exercises, but using list comprehension to solve problems rather than the beginner style loops shown in the book. There is one example where it asks for a list of numbers to be put into a list of even numbers only, BUT they must be in sublists so that if the numbers follow after one another without being interrupted by an odd number, they should be put into a sublist together:

my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
desired_output = [[2],[8],[10,12,14],[32]]

So you can see in the desired output above, 10,12,14 are evens that follow on from one another without being interrupted by an odd, so they get put into a sublist together. 8 has an odd on either side of it, so it gets put into a sublist alone after the odds are removed.

I can put together an evens list easily using list comprehension like this below, but I have no idea how to get it into sublists like the desired output shows. Could someone please suggest an idea for this using list comprehension (or generators, I don't mind which as I'm trying to learn both at the moment). Thanks!

evens = [x for x in my_list if x%2==0]
print(evens)
[2, 8, 10, 12, 14, 32]

Upvotes: 2

Views: 1178

Answers (3)

user3850
user3850

Reputation:

You mentioned also wanting to learn generators, so here is a version that's also a bit more readable, imho.

from itertools import groupby


def is_even(n):
    return n%2 == 0


def runs(lst):
    for even, run in groupby(lst, key=is_even):
        if even:
            yield list(run)


if __name__ == '__main__':
    lst = [2, 3, 5, 7, 8, 9, 10, 12, 14, 15, 17, 25, 31, 32]
    res = list(runs(lst))
    print(res)

Incidentally, if you absolutely, positively want to implement it as a list comprehension, this solutions falls out of the above quite naturally:

[list(run) for even, run in groupby(lst, key=is_even) if even]

Upvotes: 2

jpp
jpp

Reputation: 164843

As explained in the comments, list comprehensions should not be deemed "for beginners" - first focus on writing your logic using simple for loops.

When you're ready, you can look at comprehension-based methods. Here's one:

from itertools import groupby

my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]

condition = lambda x: all(i%2==0 for i in x)
grouper = (list(j) for _, j in groupby(my_list, key=lambda x: x%2))

res = filter(condition, grouper)

print(list(res))

# [[2], [8], [10, 12, 14], [32]]

The main point to note in this solution is nothing is computed until you call list(res). This is because filter and generator comprehensions are lazy.

Upvotes: 3

jferard
jferard

Reputation: 8190

If you don't want to use itertools, there's another way to do it with list comprehensions.

First, take the indices of the odd elements:

[i for i,x in enumerate(my_list) if x%2==1]

And add two sentinels: [-1] before and [len(my_list)] after:

odd_indices = [-1]+[i for i,x in enumerate(my_list) if x%2==1]+[len(my_list)]
# [-1, 1, 2, 3, 5, 9, 10, 11, 12, 14]

You have now something like that:

 [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
^---^-^-^---^-----------^--^--^--^----^

You can see your sequences. Now, take the elements between those indices. To do that, zip odd_indices with itself to get the intervals as tuples:

zip(odd_indices, odd_indices[1:])
# [(-1, 1), (1, 2), (2, 3), (3, 5), (5, 9), (9, 10), (10, 11), (11, 12), (12, 14)]    

even_groups = [my_list[a+1:b] for a,b in zip(odd_indices, odd_indices[1:])]
# [[2], [], [], [8], [10, 12, 14], [], [], [], [32]]

You just have to filter the non empty lists:

even_groups = [my_list[a+1:b] for a,b in zip(odd_indices, odd_indices[1:]) if a+1<b]
# [[2], [8], [10, 12, 14], [32]]

You can merge the two steps into one comprehension list, but that is a bit unreadable:

>>> my_list = [2,3,5,7,8,9,10,12,14,15,17,25,31,32]
>>> [my_list[a+1:b] for l1 in [[-1]+[i for i,x in enumerate(my_list) if x%2==1]+[len(my_list)]] for a,b in zip(l1, l1[1:]) if b>a+1]
[[2], [8], [10, 12, 14], [32]]

As pointed by @jpp, prefer basic loops until you feel comfortable. And maybe avoid those nested list comprehensions forever...

Upvotes: 1

Related Questions