Unexpected result after using generator expression - python

I am trying to filter some data I am working with to take out some artifacts such as negative numbers and errors in my measuring devices. I have been playing with the idea of using a generator to do this. I am using Python 2.7.2
testlist = [12,2,1,1,1,0,-3,-3,-1]
gen = (i for i, x in enumerate(testlist) if x < 0 or x > 2.5)
for i in gen: testlist.pop(i)
print testlist
This returns:
[2, 1, 1, 1, 0, -3]
My question is why is the -3 value showing up in the updated "testlist"?

When you remove items from your list, the indexes of the items after it change (they are all shifted down by one). As a result, the generator will skip over some items. Try adding some more print statements so that you can see what is going on:
for i in gen:
print i
print testlist
testlist.pop(i)
Output:
0
[12, 2, 1, 1, 1, 0, -3, -3, -1]
5
[2, 1, 1, 1, 0, -3, -3, -1]
6
[2, 1, 1, 1, 0, -3, -1]
You would have needed to delete items at index 0, 5, 5, 5. The generator produces the indexes 0, 5, 6. That makes sense because enumerate returns 0, 1, 2, ... etc. It won't return the same index twice in a row.
It's also very inefficient to remove the elements one at a time. This requires moving data around multiple times, with a worst case performance of O(n2). You can instead use a list comprehension.
testlist = [x for x in testlist if 0 <= x <= 2.5]

The better way to do this is to use a list comprehension to create a new filtered list:
testlist = [12,2,1,1,1,0,-3,-3,-1]
testlist[:] = [x for x in testlist if 0 <= x <= 2.5]
giving:
[2, 1, 1, 1, 0]

Let's consider a simpler input:
[-3, -4, -5]
First (0, -3) is taken from the enumerator. 0 is added to the generator. The for loop notices that a new element is available from the generator and removes -3:
[-4, -5]
Take a new element from the enumerator. The enumerator remembers taking the first element, so it will now take the second: -5. -5 is removed from the list in the same way. -4 remains.
By the way, an easier way to do what you're trying is the following:
testlist = filter(lambda x: x >= 0 and x <= 2.5, testlist)

You are modifying the list you are working on, somewhat analogous to modifying the index value of, for instance, a for-loop from inside the loop, in some other languages. Consider this approach as an alternative:
testlist = [x for x in testlist if x >= 0 and x <= 2.5]
using list comprehension should work more directly, though it's not a generator expression, but could trivially changed to one:
testlist = (x for x in testlist if x >= 0 and x <= 2.5)

Related

How to get only negative numbers from a list in python

I'm trying to make def get_neg(): so that get_neg(lst) returns "-1, -2, -3" when lst = [-1, -2, -3, 4, 5, 6]. I tried this code below, but I don't know how to get the result in 1 line.
def get_neg():
for n in lst:
if n < 0:
print(str(n) + ',')
else:
pass
This way I get each numbers in a different row and a comma at the end. It has to be programmed with basic commands
You can use list comprehensions (Do not forget to give lst as an argument to the get_neg function):
def get_neg(lst):
return [x for x in lst if x < 0]
Example
get_neg([-1,-2,4,5]) # [-1, -2]
get_neg([-15,-22,-4, 5, 0]) # [-15, -22, -4]

How can be used a list comprehension at left side of an assignment operator?

My code part is:
v=[7,0,2,5,1] # list with any numbers
x={2,0,4} # set of indexes (where element of x < len(v) has been provided)
for i in x: # this for loop is working
v[i]-=10
print(v) # good result
[-3, 0, -8, 5, -9]
My question is how can I replace the for cycle above with list comprehension or other more compact expression?
Note: the print only shows the new values of v
but the task would be modifying only some element of original v list.
This doesn't quite answer the question but unless you're trying for some code golfing contest I hope you just go with the below. It is simple and clear - just because the code will fit in one line doesn't make it better.
for i in x:
v[i] -= 10
I think you need:
v = [i-10 if idx in x else i for idx,i in enumerate(v)])
print(v)
Output:
[-3, 0, -8, 5, -9]
May be there is not too much reason to do a list comprehension on the left side of assignment op.,
moreover of modify assignment op. but nevertheless I wanted to construct this.
So the question was how could I modify some element by an index set
in a longer list with list comprehension (without any copy of list or usage of explicit cycle statement) .
I could achieve this solution only with "self writing" code by using exec() function.
First of all I show a simple assignment with same value:
# Let the index set and the "long" list be:
>>> x={0, 2, 4}
>>> v=[0]*10
>>> str(tuple("v["+str(i)+"]" for i in x))+"="+str(len(x)*(1,))
"('v[0]', 'v[2]', 'v[4]')=(1, 1, 1)"
# So I needed to change ("'" to "") at the left side before I could execute it.
>>> str(tuple("v["+str(i)+"]" for i in x)).replace("'","")+"="+str(len(x)*(1,))
'(v[0], v[2], v[4])=(1, 1, 1)'
# Put it into `exec()` function.
>>> exec(str(tuple("v["+str(i)+"]" for i in x)).replace("'","")+"="+str(len(x)*(1,)))
>>> v
[1, 0, 1, 0, 1, 0, 0, 0, 0, 0]
Now I show how I can decrease the list element by 1 according x set (as it was my question):
>>> s1=str(tuple("v["+str(i)+"]-=1" for i in x))
>>> s1
"('v[0]-=1', 'v[2]-=1', 'v[4]-=1')"
# Now I used the REG module for more efficient character replacement.
>>> import re as r
>>> r.sub(r"\(|\)|'","",s1).replace(",",";")
v[0]-=1; v[2]-=1; v[4]-=1'
# Now put them together:
>>> exec(r.sub(r"\(|\)|'","",str(tuple("v["+str(i)+"]-=1" for i in x))).replace(",",";"))
>>> v
[-1, 0, -1, 0, -1, 0, 0, 0, 0, 0] # if v was 10*[0]
You can try list comprehension:
print([v - 10 if i in x else v for i,v in enumerate(v)])
I think below is what you need
[v[i]-10 if i in x else v[i] for i in range(len(v))]
[-3, 0, -8, 5, -9]

Sorting only a single value in an array

Given an array, say array = [3,5,-1,4,2,-1,5,0,-1], I want to sort it such that everything stays in the same place, except the -1's which will move to the end, so:
>>> function(array)
[3,5,4,2,5,0,-1,-1,-1]
This could also be generalised to any number, however, I'm using -1 as a key value and need them to all be at the end of the array for some later processing.
I've tried playing with the sort() and sorted() inbuilt functions in python however neither of these seemed to be able to do what I needed.
You can use a lambda function in your call to sort. Here we sort the values by returning a 0 if the value is anything other than -1, otherwise 1. This will push all of the -1 values to the right.
array.sort(key=lambda x: x==-1)
array
# returns
[3, 5, 4, 2, 5, 0, -1, -1, -1]
This is one approach.
Ex:
array = [3,5,-1,4,2,-1,5,0,-1]
num = -1
print([i for i in array if i != num] + [i for i in array if i == num])
Output:
[3, 5, 4, 2, 5, 0, -1, -1, -1]
Python sort is stable, so you can just move some items around, leaving the others untouched
lst = [3,5,-1,4,2,-1,5,0,-1]
print(sorted(lst,key = lambda x : x == -1))
result: [3, 5, 4, 2, 5, 0, -1, -1, -1]
with this key, all items which are equal to -1 yield True for the sort key, making them rise to the end of the list. Others are left where they are relatively to each other (because sort key yields the same False value for all of them).

Move zeroes to end of list

I am working on moving all zeroes to end of list. .. is this approach bad and computationally expensive?
a = [1, 2, 0, 0, 0, 3, 6]
temp = []
zeros = []
for i in range(len(a)):
if a[i] !=0:
temp.append(a[i])
else:
zeros.append(a[i])
print(temp+zeros)
My Program works but not sure if this is a good approach?
A sorted solution that avoids changing the order of the other elements is:
from operator import not_
sorted(a, key=not_)
or without an import:
sorted(a, key=lambda x: not x) # Or x == 0 for specific numeric test
By making the key a simple boolean, sorted splits it into things that are truthy followed by things that are falsy, and since it's a stable sort, the order of things within each category is the same as the original input.
This looks like a list. Could you just use sort?
a = [1, 2, 0, 0, 0, 3, 6]
a.sort(reverse=True)
a
[6, 3, 2, 1, 0, 0, 0]
To move all the zeroes to the end of the list while preserving the order of all the elements in one traversal, we can keep the count of all the non-zero elements from the beginning and swap it with the next element when a non-zero element is encountered after zeroes.
This can be explained as:
arr = [18, 0, 4, 0, 0, 6]
count = 0
for i in range(len(arr):
if arr[i] != 0:
arr[i], arr[count] = arr[count], arr[i]
count += 1
How the loop works:
when i = 0, arr[i] will be 18, so according to the code it will swap with itself, which doesn't make a difference, and count will be incremented by one. When i=1, it will have no affect as till now the list we have traversed is what we want(zero in the end). When i=4, arr[i]= 4 and arr[count(1)]= 0, so we swap them leaving the list as[18, 4, 0, 0, 0, 6] and count becomes 2 signifying two non-zero elements in the beginning. And then the loop continues.
You can try my solution if you like
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
for num in nums:
if num == 0:
nums.remove(num)
nums.append(num)
I have tried this code in leetcode & my submission got accepted using above code.
Nothing wrong with your approach, really depends on how you want to store the resulting values. Here is a way to do it using list.extend() and list.count() that preserves order of the non-zero elements and results in a single list.
a = [1, 2, 0, 0, 0, 3, 6]
result = [n for n in a if n != 0]
result.extend([0] * a.count(0))
print(result)
# [1, 2, 3, 6, 0, 0, 0]
You can try this
a = [1, 2, 0, 0, 0, 3, 6]
x=[i for i in a if i!=0]
y=[i for i in a if i==0]
x.extend(y)
print(x)
There's nothing wrong with your solution, and you should always pick a solution you understand over a 'clever' one you don't if you have to look after it.
Here's an alternative which never makes a new list and only passes through the list once. It will also preserve the order of the items. If that's not necessary the reverse sort solution is miles better.
def zeros_to_the_back(values):
zeros = 0
for value in values:
if value == 0:
zeros += 1
else:
yield value
yield from (0 for _ in range(zeros))
print(list(
zeros_to_the_back([1, 2, 0, 0, 0, 3, 6])
))
# [1, 2, 3, 6, 0, 0, 0]
This works using a generator which spits out answers one at a time. If we spot a good value we return it immediately, otherwise we just count the zeros and then return a bunch of them at the end.
yield from is Python 3 specific, so if you are using 2, just can replace this with a loop yielding zero over and over.
Numpy solution that preserves the order
import numpy as np
a = np.asarray([1, 2, 0, 0, 0, 3, 6])
# mask is a boolean array that is True where a is equal to 0
mask = (a == 0)
# Take the subset of the array that are zeros
zeros = a[mask]
# Take the subset of the array that are NOT zeros
temp = a[~mask]
# Join the arrays
joint_array = np.concatenate([temp, zeros])
I tried using sorted, which is similar to sort().
a = [1, 2, 0, 0, 0, 3, 6]
sorted(a,reverse=True)
ans:
[6, 3, 2, 1, 0, 0, 0]
from typing import List
def move(A:List[int]):
j=0 # track of nonzero elements
k=-1 # track of zeroes
size=len(A)
for i in range(size):
if A[i]!=0:
A[j]=A[i]
j+=1
elif A[i]==0:
A[k]=0
k-=1
since we have to keep the relative order. when you see nonzero element, place that nonzero into the index of jth.
first_nonzero=A[0] # j=0
second_nonzero=A[1] # j=1
third_nonzero=A[2] # j=2
With k we keep track of 0 elements. In python A[-1] refers to the last element of the array.
first_zero=A[-1] # k=-1
second_zero=A[-2] # k=-2
third_zero= A[-3] # k=-3
a = [4,6,0,6,0,7,0]
a = filter (lambda x : x!= 0, a) + [0]*a.count(0)
[4, 6, 6, 7, 0, 0, 0]

Python map a value to each i-th sublist's element

I'm trying to do the following in python: given a list of lists and an integer i
input = [[1, 2, 3, 4], [1, 2, 3, 4], [5, 6, 7, 8]]
i = 1
I need to obtain another list which has all 1s for the elements of the i-th list, 0 otherwise
output = [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0]
I wrote this code
output = []
for sublist in range(0, len(input)):
for item in range(0, len(input[sublist])):
output.append(1 if sublist == i else 0)
and it obviously works, but since I'm a newbie in python I suppose there's a better 'pythonic' way of doing this.
I thought using map could work, but I can't get the index of the list with it.
Creating extra variable to get index of current element in interation is quite unpythonic. Usual alternative is usage of enumerate built-in function.
Return an enumerate object. sequence must be a sequence, an iterator,
or some other object which supports iteration. The next() method of
the iterator returned by enumerate() returns a tuple containing a
count (from start which defaults to 0) and the values obtained from
iterating over sequence.
You may use list comprehension with double loop inside it for concise one liner:
input_seq = [[1, 2, 3, 4], [1, 2, 3, 4], [5, 6, 7, 8]]
i = 1
o = [1 if idx == i else 0 for idx, l in enumerate(input_seq) for _ in l]
Alternatively,
o = [int(idx == i) for idx, l in enumerate(input_seq) for _ in l]
Underscore is just throwaway name, since in this case we don't care for actual values stored in input sublists.
Here's a 1-liner, but it's not really obvious:
output = [int(j == i) for j, sublist in enumerate(input) for _ in sublist]
Somewhat more obvious:
output = []
for j, sublist in enumerate(input):
output.extend([int(i == j)] * len(sublist))
Then "0 or 1?" is computed only once per sublist, which may or may not be more efficient.

Categories

Resources