Python List comprehension missing info - python

I am trying to implement the following code into a list comprehension:
def incrementValues(m):
for i in range(len(m)):
for ii in range(len(m[i])):
if m[i][ii] % 2 == 0:
m[i][ii] //=2
return m
m = [[5, 4], [2, 3], [6, 7]]
print(incrementValues(m))
So far I have :
[[m for m in range(len(m))] for n in range(len(m))]
but I cannot work out where this info goes:
if m[i][ii] % 2 == 0:
m[i][ii] //=2
If someone could steer me in the right direction, that would be great! (Also, feel free to advise if my original code could be written in a better way)
Thanks :)

Your code doesn't create a list, so it probably shouldn't be a list comprehension, which is for creating lists.
Now, in general it is bad for a function to mutate it's inputs. So indeed, you could create a new list instead of modifying your list, and you could do that with a list comprehension:
[[x//2 if x % 2 == 0 else x for x in sub] for sub in m]
but this is doing something different than your code. It is important to understand that.
Note, if you must modify the list, then you should do it this way:
def increment_values(m): # stick to python naming conventions
for sub in m:
for i, val in enumerate(sub):
if val % 2 == 0:
sub[i] //= 2
return m
Of course, you can also use slice-assignment with a list-comprehension, although, this would make your code less space efficient (which is one nice aspect of modifying the list in-place):
def increment_values(m): # stick to python naming conventions
m[:] = [[x//2 if x % 2 == 0 else x for x in sub] for sub in m]
return m
But I would prefer the for-loop version if you are going to do this at all (and I would prefer a version that doesn't mutate the list to begin with...)

The corresponding list comprehension is
m = [[5, 4], [2, 3], [6, 7]]
res = [[item//2 if item%2==0 else item for item in items] for items in m]

Related

Assinging a new value to item in list during for loop doesnt work?

I have a list of items. In a for loop i check if the item is 3. And if it is 3 then it should change the 3 to a 2. These are the two ways that came to my mind using a for loop. But only the last one does work.
Is there a way I can make the first for loop work without losing its "pythonic" style?
a = [1, 2, 3]
for num in a:
if num == 3:
num = 2
# -> [1, 2, 3]
for i in range(len(a)):
if a[i] == 3:
a[i] = 2
# -> [1, 2, 2]
There's no way to assign to a bare name (num) and have it affect a container (list a). You need to use an index. Although, it is more Pythonic to use enumerate:
for i, num in enumerate(a):
if num == 3:
a[i] = 2
Another option is to use a full-slice assignment to totally overwrite the contents of the list from a generator expression:
a[:] = (2 if num==3 else num for num in a)
Let's take a look at this code:
for i in [1, 2, 3]:
...
In the for loop, the [1, 2, 3] is a list object. The i is just a variable that holds a pointer (basically a reference to the data). When you do an operation like i = 3 in the loop, the variable i is set to hold the number 3, but the actual list is not changed. List comprehension can be used for what you're trying to accomplish:
a = [1, 2, 3]
l = [2 if num == 3 else num for num in a]
# note that this is a ternary operator in a list comprehension
If you wish to use a for loop, then then enumerate method with index assignment will do the trick:
a = [1, 2, 3]
for i, num in enumerate(a):
if num == 3:
a[i] = 2
You can also do it manually like so:
i = 0
for num in a:
if num == 3:
a[i] = 2
i += 1
Note that:
list comprehension creates a new list (and doesn't edit the old one)
the enumeration method I showed above does edit the original list (but may be slower, this is based on your machine though)
the final option I put just to illustrate what the enumerate method does and to show that it is an option
To modify in-place, this is perhaps more Pythonic:
for i, n in enumerate(a):
if n == 3:
a[i] = 2
This may also be preferable if you have a lengthy test for how an item is replaced, such that a list comprehension may be unwieldy.
Try a list comprehension:
>>> a = [1, 2, 3]
>>> a = [2 if num == 3 else num for num in a]
>>> a
[1, 2, 2]
If not a list comprehension, you could do use an enumeration:
a = [1,2,3]
for count, num in enumerate(a):
if num == 3:
a[count] = 2
A map also works (seriously just use a list comprehension), but it's a bit less pythonic (lambdas are also confusion to some people):
a = [1,2,3]
a = list(map(lambda num: 2 if num==3 else num, a))
Another thing you can do is, if the variable you're iterating over is some object with a method containing side effects (like a setter), you could use that in-place. For example, imagine I have some myInteger class that inherits from int with a method that lets me set some value. Let's say it's something like:
myInt = myInteger(5)
print(myInt.value)
>> 5
myInt.set_value(6)
print(myInt.value)
>>6
This would give you the interface you're looking for:
for num in a:
if num == 3:
a.set_value(2)
The trade-off being that you're writing a weird class to do this, which will lead to confusion in the future.

How to parse these operations through lists?

Program description:
Program accepts a list l containing other lists. Output l where lists with length greater than 3 will be changed accordingly: the element with index 3 is going to be a sum of removed elements (from third to the end).
My solution:
l = [[1,2], [3,4,4,3,1], [4,1,4,5]]
s = 0
for i in range(len(l)-1):
if len(l[i]) > 3:
for j in range(3,len(l[i])-1):
s += l[i][j]
l[i].remove(l[i][j])
l[i].insert(len(l[i]),s)
l
Test:
Input: [[1,2], [3,4,4,3,1], [4,1,4,5]]
Expected Output: [[1, 2], [3, 4, 8], [4, 1, 9]]
Program run:
Input: [[1,2], [3,4,4,3,1], [4,1,4,5]]
Output: [[1, 2], [4, 4, 3, 1, 3], [4, 1, 4, 5]]
Question: I don't understand what can be the source of the problem in this case, why should it add some additional numbers to the end, instead of summ. I will appreciate any help.
remove is the wrong function. You should use del instead. Read the documentation to understand why.
And another bug you have is that you do not reset s. It should be set to 0 in the outer for loop.
But you're making it too complicated. I think it's better to show how you can do it really easy.
for e in l: # No need for range. Just iterate over each element
if len(e) > 3:
e[2]=sum(e[2:]) # Sum all the elements
del(e[3:]) # And remove
Or if you want it as a list comprehension that creates a new list and does not alter the old:
[e[0:2] + [sum(e[2:])] if len(e)>3 else e for e in l]
First of all, remove() is the wrong method, as it deletes by value, not index:
Python list method remove() searches for the given element in the list
and removes the first matching element.
You'd want to use del or pop().
Second of all, you're not slicing all of the elements from the end of the list, but only one value.
remove is reason why your code is not working. (as mentioned by Mat-KH in the other answer)
You can use list comprehension and lambda function to make it a two liner.
func = lambda x: x if len(x) < 3 else x[:2] + [sum(x[2:])]
l = [func(x) for x in l]

python for loop with lists

I get:
if(lst[i]%2!=0):
IndexError: list index out of range
from this code:
lst=[1,2,3,4,5,6]
for i in range(len(lst)):
if(lst[i]%2!=0):
lst.remove(lst[i])
print(lst)
I am trying to print only the even numbers in a list and I do not see a problem with my code
why am I getting this error ?
You should not remove from a list while iterating it. Use, for instance, a list comprehension and slice assignment to achieve the same modification:
lst = [1,2,3,4,5,6]
lst[:] = [x for x in lst if x % 2 == 0]
# or simply (if you don't need to mutate the original list)
# lst = [x for x in lst if x % 2 == 0]
print(lst)
# [2, 4, 6]
This has also better time complexity (linear) whereas the repeated remove approach is quadratic.
Instead of removing the item from the original list, you can also split the original list into 2 new lists, evens and odds
lst=[1,2,3,4,5,6]
evens = []
odds = []
for i in lst):
if(i % 2 != 0):
odds.append(i)
else:
evens.append(i)
print(evens)

Get complement (opposite) of list slice

Is there syntax to get the elements of a list not within a given slice?
Given the slice [1:4] it's easy to get those elements:
>>> l = [1,2,3,4,5]
>>> l[1:4]
[2, 3, 4]
If I want the rest of the list I can do:
>>> l[:1] + l[4:]
[1, 5]
Is there an even more succinct way to do this? I realize that I may be being too needy because this is already very concise.
EDIT: I do not think that this is a duplicate of Invert slice in python because I do not wish to modify my original list.
If you want to modify the list in-place, you can delete the slice:
>>> l = [1, 2, 3, 4, 5]
>>> del l[1:4]
>>> l
[1, 5]
Otherwise your originally suggestion would be the most succinct way. There isn't a way to get the opposite of a list slice using a single slice statement.
Clearly the best solution to create a class to encapsulate some magical behavior that occurs when you use 'c' as the step value. Clearly.
class SuperList(list):
def __getitem__(self, val):
if type(val) is slice and val.step == 'c':
copy = self[:]
copy[val.start:val.stop] = []
return copy
return super(SuperList, self).__getitem__(val)
l = SuperList([1,2,3,4,5])
print l[1:4:'c'] # [1, 5]
[x for i, x in enumerate(l) if i not in range(1, 4)]
Which is less concise. So the answer to your question is no, you can't do it more concisely.
I was looking for some solution for this problem that would allow for proper handling of the step parameter as well.
None of the proposed solution was really viable, so I ended up writing my own:
def complement_slice(items, slice_):
to_exclude = set(range(len(items))[slice_])
step = slice_.step if slice_.step else 1
result = [
item for i, item in enumerate(items) if i not in to_exclude]
if step > 0:
return result
else:
return result[::-1]
ll = [x + 1 for x in range(5)]
# [1, 2, 3, 4, 5]
sl = slice(1, 4)
ll[sl]
# [2, 3, 4]
complement_slice(ll, sl)
# [1, 5]
To the best of my knowledge, it does handle all the corner cases as well, including steps, both positive and negative, as well as repeating values.
I wanted to write it as a generator, but I got annoyed by checking all corner cases for positive/negative/None values for all parameters.
In principle, that is possible, of course.
You can use list comprehension with loop
l = [i for i in l if i not in l[1:4]]

Python list recursive changes

I have a bug in my attempt to add to a list a sequence of numbers recursively. E.g. if the input is [5,3,9], I do [5+1,3+2,9+3] and output [6,5,12]. I want to do this recursively so the way I'm doing it is going through and adding one to a smaller and smaller part of the list as below:
def add_position_recur(lst, number_from=0):
length = len(lst)
# base case
if (length <= 1):
lst = [x+1 for x in lst]
print "last is", lst
else:
lst = [x+1 for x in lst]
print "current list is", lst
add_position_recur(lst[1:], number_from)
return lst
The problem, though, is that all this does is add 1 to every element of the list. Where is the bug? Is it to do with the way I return the list in the base case?
When you recurse down your call stack you slice lst which creates a new list, this is not the same as what you return, so you will only ever return the changes you've applied to your list in the first call to the function, losing all changes further down the stack:
>>> add_position_recur([1,2,3])
[2, 3, 4]
This should have returned [2, 4, 6].
You need to consider reassembling the list on the way out to get the changes.
return [lst[0]] + add_position_recur(lst[1:], number_from)
and you need to return lst in your base case:
def add_position_recur(lst, number_from=0):
length = len(lst)
# base case
if (length <= 1):
lst = [x+1 for x in lst]
return lst
else:
lst = [x+1 for x in lst]
return [lst[0]] + add_position_recur(lst[1:], number_from)
>>> add_position_recur([1,2,3])
[2, 4, 6]
However, this is quite a complicated approach to this recursion. It is idiomatic for the base case to be the empty list, otherwise take the head and recurse down the tail. So something to consider which uses the number_from:
def add_position_recur(lst, number_from=1):
if not lst:
return lst
return [lst[0]+number_from] + add_position_recur(lst[1:], number_from+1)
>>> add_position_recur([1,2,3])
[2, 4, 6]
This also has the advantage(?) of not changing the passed in lst
Why don't you instead do something like this:
def func(lon, after=[]):
if not l:
pass
else:
v = len(lon) + lon[-1]
after.append(v)
func(lon[:-1], after)
return after[::-1]
The output of the function for the example you provided matches what you want.
Currently, you are simply adding 1 to each value of your list.
lst = [x+1 for x in lst]
Rather, you should be increasing a variable which is being added to x with each iteration of x in lst.
lst = [x+(lst.index(x)+1) for x in lst]
This solution assumes that you want the number being added to x to depend on its position in the list relative to the start of the list, rather than being dependent on the position of x relative to the first element which was >1. Meaning, do you want to add 1 or 3 to the value 2 in the following list? The solution above adds three.
lst = [0.5, 0.1, 2, 3]

Categories

Resources