Python: list comprehension to produce consecutive sublists of even numbers - python

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]

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.

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]

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...

Related

How to pass to a function that takes two arguments an element from one list together with each element of another list?

I am new to Python and I need help with following.
I do have a list a = [range(1, 50, 10)]
and another list b = [2, 4, 5, 8, 12, 34]
Now, I have a function that calculates something, let's call it "SomeFunction", that takes two arguments.
I want to pass to this SomeFunction for element from list a each element of list b. So, I want values for: SomeFunction(1, 2), SomeFunction(1, 4), SomeFunction(1, 5), ..... SomeFunction(50, 2), SomeFunction(50, 4), SomeFunction(50, 5), etc.
I think it should be done somehow with for loops, but I do not know how...
You'd need a nested for loop:
a = range(1, 50, 10)
b = [2, 4, 5, 8, 12, 34]
for aval in a:
for bval in b:
print(aval, bval) # or any other function call
This just goes through all values in b for each value in a. (Note that you don't want range inside square brackets as you have in your question, I removed those).
A more advanced version: "all pairs of values" is also known as the Cartesian product, which is provided by the itertools module. Thus you could write
from itertools import product
for aval, bval in product(a, b):
print(aval, bval)
Try this:
a = range(1, 50, 10)
b = [2, 4, 5, 8, 12, 34]
for i in a:
for n in b:
print(SomeFunction(i, n))

Adding values to an empty 2D list (TypeError)

I'm trying to add values to an empty 2d list with this code:
values_to_go = [(7, 6, 2, 2, 350.0, '6', '11/05/2022\n', 7), (8, 6, 2, 1, 350.0, '08:30-10:30\n', '15/06/2022\n', 7), (9, 6, 2, 1, 350.0, '16:00-18:00\n', '25/08/2022\n', 7)]
list = []
for i in range(len(values_to_go)):
list[i][0] = values_to_go[i][0]
list[i][1] = values_to_go[i][5]
list[i][2] = values_to_go[i][6]
list[i][3] = values_to_go[i][2]
print(list)
But I'm getting this error:
TypeError: 'int' object is not subscriptable
Expected output: values_to_go = [(7, 6, 11/05/2022\n, 2), (8, 08:30-10:30\n, 15/06/2022\n, 2), (9, 16:00-18:00\n, 25/08/2022\n, 2)]
You have many mistakes in this code.
The values_to_go list is not a 2d list. The (7) or any number between parentheses is totally equal to a number without any parentheses. If you want to make it a tuple, you should tell python that these are not parentheses around the number; but these are tuple signs. So 7 is totally equal to (7). You should use (7,) to tell python that this is a tuple with one member which is 7. So this ... = values_to_go[i][0] will be OK.
Another mistake is the part that you've written to append members into the empty list. You cannot point to a non-existent member of a list with this expression: list[i][0]. There is no list[i] and therefore obviously there is no list[i][0]. If you want to append into the end of the list, you should just do it:
list.append(anything)
This would be something like this:
>>> values_to_go = [(7,), (8,), (9,)] # or [[7], [8], [9]]
>>> some_list = []
>>> for i in range(len(values_to_go)):
... some_list.append(values_to_go[i][0])
...
>>> print(some_list)
[7, 8, 9]
>>> # and if you want to make it 2D:
>>> some_list = []
>>> for i in range(len(values_to_go)):
... some_list.append([values_to_go[i][0]])
...
>>> print(some_list)
[[7], [8], [9]]
When you create your list, it is empty, so basically you can't access any position of it, first you have to create them you can do this with the .append() function.
Your code could look like this:
mylist = [[],[],[]]
for i in range(len(values_to_go)):
mylist[i].append(values_to_go[i][0])
mylist[i].append(values_to_go[i][5])
mylist[i].append(values_to_go[i][6])
mylist[i].append(values_to_go[i][2])
output: [[7, '6', '11/05/2022\n', 2], [8, '08:30-10:30\n', '15/06/2022\n', 2], [9, '16:00-18:00\n', '25/08/2022\n', 2], ]
Some extra tip, list is a Python reserved word, so don't use it as a variable name

Combining numbers in the most efficient way

I'm looking into a way to combine a data-set with numbers in such a way that the total number of combinations is minimized. The constrain is that the sum of every combination need to be smaller than a certain number.
An example of the problem could look like this:
Data-set: [11, 10, 19, 2, 12]
Constrain: Sum of combination =< 21 and every number can only used once.
Manually you can find that the most optimal combination is: [11, 10], [19, 2], [12]
Goal: Minimize number of combinations
However for large data-sets it is not possible to do by hand.
I have looked among other things into the possibility to use, permutations and linear systems of equations, but to be honest, I don't really have an idea how to solve the problem. Could somebody provide me with some ideas? Thank you in advance.
In absence of further clarification, the following algorithm produces the expected output:
def split_max(items, max_sum):
result = []
partial = []
for item in items:
if sum(partial) < max_sum:
partial.append(item)
else:
result.append(partial)
partial = [item]
if partial:
result.append(partial)
return result
split_max([11, 10, 19, 2, 12], 21)
# [[11, 10], [19, 2], [12]]
(note that this does not necessarily produce the minimum number of sub-lists if it is allowed for the items to appear in different order).
All you need to itertools.combinations
>>> from itertools import combinations
>>> myList = [11,10,19,2,12]
>>> [i for i in combinations(myList, 2) if sum(i) <= 21] # first will be your iterable, second is the position here you need combination for 2 position
[(11, 10), (11, 2), (10, 2), (19, 2), (2, 12)]
>>>

Unpack a List in to Indices of another list in python

Is it possible to unpack a list of numbers in to list indices? For example I have a lists with in a list containing numbers like this:
a = [[25,26,1,2,23], [15,16,11,12,10]]
I need to place them in a pattern so i did something like this
newA = []
for lst in a:
new_nums = [lst[4],lst[2],lst[3],lst[0],lst[1]]
newA.append(new_nums)
print (newA) # prints -->[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]
so instead of writing new_nums = [lst[4],lst[2],lst[3],lst[0],lst[1]] , i thought of defining a pattern as list called pattern = [4,2,3,0,1] and then unpack these in to those indices of lst to create new order of lst.
Is there a fine way to do this.
Given a list of indices called pattern, you can use a list comprehension like so:
new_lst = [[lst[i] for i in pattern] for lst in a]
operator.itemgetter provides a useful mapping function:
from operator import itemgetter
a = [[25,26,1,2,23], [15,16,11,12,10]]
f = itemgetter(4,2,3,0,1)
print [f(x) for x in a]
[(23, 1, 2, 25, 26), (10, 11, 12, 15, 16)]
Use list(f(x)) if you want list-of-lists instead of list-of-tuples.
If you're not opposed to using numpy, then try something like:
import numpy as np
pattern = [4, 2, 3, 0, 1]
newA = [list(np.array(lst)[pattern]) for lst in a]
Hope it helps.
In pure Python, you can use a list comprehension:
pattern = [4,2,3,0,1]
newA = []
for lst in a:
new_nums = [lst[i] for i in pattern]
newA.append(new_nums)
In numpy, you may use the fancy indexing feature:
>>> [np.array(lst)[pattern].tolist() for lst in a]
[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]
it is slower than other, but it is another option. you can sort the list based on your pattern
a = [[25,26,1,2,23], [15,16,11,12,10]]
pattern = [4,2,3,0,1]
[sorted(line,key=lambda x:pattern.index(line.index(x))) for line in a]
[[23, 1, 2, 25, 26], [10, 11, 12, 15, 16]]

Adding difference of pair of numbers in list using map/reduce

I am practicing exercises for functional programming concepts using python. I came across this problem. I have tried a lot and couldn't find a solution using functional programming constructs like map/reduce, closures.
Problem: Given a list of numbers
list = [10, 9, 8, 7, 6, 5, 4, 3]
Find sum of difference in each pair using Map/Reduce or any functional programming concepts e.g
[[10 -9] + [8 - 7] + [6 -5] + [4 - 3]] = 4
For me tricky part is isolating pairs using map/reduce/recursion/closure
The recursive relationship you are looking for is
f([4, 3, 2, 1]) = 4 - 3 + 2 - 1 = 4 - (3 - 2 + 1) = 4 - f([3, 2, 1])
One of the mantras followed by many functional programmers is the following:
Data should be organized into data structures that mirror the processing you want to perform on it
Applying this to your question, you're running into a simple problem: the list data structure doesn't encode in any way the relationship between the pairs that you want to operate on. So map/reduce operations, since they work on the structure of lists, don't have any natural visibility into the pairs! This means you're "swimming against the current" of these operations, so to speak.
So the first step should be to organize the data as a list or stream of pairs:
pairs = [(10, 9), (8, 7), (6, 5), (4, 3)]
Now after you've done this, it's trivial to use map to take the difference of the elements of each pair. So basically I'm telling you to split the problem into two easier subproblems:
Write a function that groups lists into pairs like that.
Use map to compute the difference of each pair.
And the hint I'll give is that neither map nor reduce is particularly useful for step #1.
using itertools.starmap:
l = [10, 9, 8, 7, 6, 5, 4, 3]
from operator import sub
from itertools import starmap
print(sum(starmap(sub, zip(*[iter(l)] * 2))))
4
Or just a lambda:
print(sum(map(lambda x: sub(*x), zip(*[iter(l)] * 2))))
Or range and itemgetter:
from operator import itemgetter as itgt
print(sum(itgt(*range(0, len(l), 2))(l)) - sum(itgt(*range(1, len(l), 2))(l)))
Another try may be isolating pairs-You may need to sort the list beforehand
>>>import operator
>>>l=[10, 9, 8, 7, 6, 5, 4, 3]
>>>d= zip(l,l[1:])
>>>w=[d[i] for i in range(0,len(d),2)]#isolate pairs i.e. [(10, 9), (8, 7), (6, 5), (4, 3)]
>>>reduce(operator.add,[reduce(operator.sub,i) for i in w])
>>>4
You can do it in simple way.
l = [10, 9, 8, 7, 6, 5, 4, 3]
reduce(lambda x, y : y - x, l) * -1

Categories