Related
I have a list of values and I want to move certain (or all) values to another list if they exist in a reference list.
x = [2,3,4,5,6,7,8] # list of values
ref = [2,3,4,5,6,7,8] # reference list
result = [x.pop(i) for i, v in enumerate(x) if v in ref]
But because of popping the current index, it ends up giving every other value instead. Is there a nice straightforward way to do this?
What I want at the end of this example is x=[] and result=[2,3,4,5,6,7,8], but ref doesn't need to contain all elements of x, this was just for an example. In another case it might be:
x = [2,3,4,5,6,7,8] # list of values
ref = [2,6,7] # reference list
So then I want x = [3,4,5,8] and result = [2,6,7]
In general, we should avoid modifying objects while iterating over them.
For this problem, we could generate result and filter x in two steps using comprehensions (avoiding appending to lists) as in the following example.
result, x = [v for v in x if v in ref], [v for v in x if v not in ref]
You could do it the old-fashioned way, with a while loop and a pointer into x:
x = [2, 3, 4, 5, 6, 7, 8]
ref = [2, 6, 7]
result = []
i = 0
while i < len(x):
if x[i] in ref:
result.append(x.pop(i))
else:
i += 1
print(x)
print(result)
Output:
[]
[2, 3, 4, 5, 6, 7, 8]
You can simply iterate from the end to the start to avoid pop() changing the list size while iterating. Just call reverse() on your new list after running your loop if the order of the list matters.
I have this list of values:
A = [0,0,1,2,3,4,5,6,0,6,6,8,8,0,0,2,3,4,5,12,45,-0,-0,-9,-2,3,-0,-2,-2,-2]
I want to get this list of values for the output :
A = [1,2,3,4,5,6,0,6,6,8,8,2,3,4,5,12,45,-9,-2,3,-0,-2,-2,-2]
Basically, I want to drop the consecutive zeros only, and keep all the other values.
Do you have any idea on how i can do that ? I tried this one but i know there will be in index error :
X = []
for j in range(len(A)):
if A[j] != 0 and A[j+1] != 0:
X.append(A[j])
else:
print('lol')
print(X)```
You can use itertools.groupby and itertools.chain:
from itertools import groupby, chain
out = list(chain.from_iterable(G for k,g in groupby(A)
if len(G:=list(g))<2 or k!=0))
Explanation:
groupby will group the consecutive values. For each group, if the length is no more than 1 or the key (=value) is not 0, keep it. Finally chain all the groups together and convert to list.
Note that groupby returns iterators so I am using an assignment expression to perform the conversion.
output:
[1, 2, 3, 4, 5, 6, 0, 6, 6, 8, 8, 2, 3, 4, 5, 12, 45, -9, -2, 3, 0, -2, -2, -2]
With itertools:
from itertools import groupby
X = [x
for x, [*xs] in groupby(A)
if x or len(xs) == 1
for x in xs]
Alternatively:
X = []
for x, [*xs] in groupby(A):
if x or len(xs) == 1:
X += xs
Or taking any x that's not zero or where the previous and next values are not zero (padding with 1):
X = [x
for p, x, n in zip([1]+A, A, A[1:]+[1])
if x or p and n]
if u dont want to import itertools and prefer list comprehension
A = [i for index,i in enumerate(A) if i!=0 or index not in [0,len(A)] and A[index-1]!=i and A[index+1]!=i ]
note that this expressions uses the precedence of and operator over or operator
enumerate is used too
Here's a more simple, easy, and bit lengthy than other answers method to solve your problem
A = [0,0,1,2,3,4,5,6,0,6,6,8,8,0,0,2,3,4,5,12,45,-0,-0,-9,-2,3,-0,-2,-2,-2]
ind=[i for i,j in enumerate(A) if j==0]
C=[(ind[i],ind[i+1]) for i in range(len(ind)-1) if ind[i]+1==ind[i+1]]
D=[i for i1 in C for i in i1]
E=["" if i in D else j for i,j in enumerate(A)]
F=[i for i in E if i!=""]
# TEST
d_output=[1,2,3,4,5,6,0,6,6,8,8,2,3,4,5,12,45,-9,-2,3,-0,-2,-2,-2]
print(F==d_output)
Output: 1 i.e Your desired output
If you want to specify the value, then you can wrap it up in a function as below:
def remove_c(list_2,val):
ind=[i for i,j in enumerate(list_2) if j==val]
C=[(ind[i],ind[i+1]) for i in range(len(ind)-1) if ind[i]+1==ind[i+1]]
D=[i for i1 in C for i in i1]
E=["" if i in D else j for i,j in enumerate(A)]
return [i for i in E if i!=""]
print(remove_c(A,10))
Explantion:
I have taken all the indexes of 0 in the list.
Checked if the indexes are consecutive or not. If they are, then append them in the list C in tuple.
And, because the list C contains tuple, created a flat list D out of list C.
Replaced the indexes of list D with "".
Removed "" for the list.
I have noticed a silly mistake in your code:
X = []
for j in range(len(A)): # Mistake here
if A[j] != 0 and A[j+1] != 0: # if we bypassed the above error we can also get error here
X.append(A[j])
else:
print('lol')
print(X)
The problem is when the i is at last index of the list, there would be no other index but you have hard-coded to search for index+1, so it would throw an error.
There are 2 method to solve this:
Use try and except.
Replace range(len(A) to range(len(A)-1).
Wondering if someone can help me with a small problem bit of code I have.
list1 = [1,2,4]
dz = 0.5
k = max(list1)/dz
print('k =',k)
list1_diff = np.insert(np.diff(list1),0,1)
for item in list1_diff:
#Number of times to repeat number
repeats = item/dz
vec = [np.random.normal(0,1)]*int(repeats)
print(vec)
this produces
[-0.7014481088047718, -0.7014481088047718]
[3.1264015795601274, 3.1264015795601274]
[0.44017976879654314, 0.44017976879654314, 0.44017976879654314, 0.44017976879654314]
Which is doing sort of the right thing, but I want the output of the loop to be these three lists as one single list. So it should be
[-0.7014481088047718, -0.7014481088047718, 3.1264015795601274, 3.1264015795601274, 0.44017976879654314, 0.44017976879654314, 0.44017976879654314, 0.44017976879654314]
you're not dumb. If I got you right, you want to have one list with repeated random numbers according to your formula.
You would just have to modify your code a small bit, like this:
list1 = [1,2,4]
dz = 0.5
k = max(list1)/dz
print('k =',k)
list1_diff = np.insert(np.diff(list1),0,1)
# create your empty result list, where you add the random numbers in the loop
vec= list()
for item in list1_diff:
# Number of times to repeat number
repeats = item/dz
# add the new random number with it's repeats to the vec list
# using extend
vec.extend([np.random.normal(0,1)]*int(repeats))
# print the result outside the loop, after it has been constructed
print(vec)
try this one :
import numpy as np
list1 = [1,2,4]
dz = 0.5
k = max(list1)/dz
print('k =',k)
list1_diff = np.insert(np.diff(list1),0,1)
final_list=[]
for item in list1_diff:
#Number of times to repeat number
repeats = item/dz
vec = [np.random.normal(0,1)]*int(repeats)
final_list += vec
print(final_list)
this should do the job:
import numpy as np
list1 = [1,2,4]
dz = 0.5
k = max(list1)/dz
print(f'k = {k}')
lst = [[np.random.normal(0,1)]*int(item/dz) for item in np.insert(np.diff(list1),0,1)]
flatten = lambda l: [item for sublist in l for item in sublist]
print(flatten(lst))
output:
k = 8.0
[-1.1762058341816053, -1.1762058341816053, -1.0599923573472354, -1.0599923573472354, 0.6374252888036466, 0.6374252888036466, 0.6374252888036466, 0.6374252888036466]
Heres what happened
list comprehension that creates a list full of output (mainly what you did)
flatten function applied to list
note that you can also use extend(), i was going to add it in but another answer beat me to it
Pythons lists have an inbuilt function for this:
list_name.extend([])
For example,
list_name = [1, 2, 3]
list_name.extend([4, 5, 6])
print(sample_list)
[1, 2, 3, 4, 5, 6]
So python extend function extends the list with another list. It does not appends it!
What is the most efficient way to extract to a list the indices corresponding to the n highest values of another list, while preserving the list we're extracting the indices from?
For example, let's say we have the following list of indices:
foo = [107,6,34,12,82]
If we requested the indices of the 2 highest values of the list foo, it should return us the following list:
bar = [0,4]
Here's what I'm running right now, it's really inefficient and not elegant at all and I don't really know how to improve it:
foo = [107, 6, 34, 12, 82]
tmp = list(foo)
bar = []
no_of_indices_wanted = int(input())
for n in range(no_of_indices_wanted):
bar.append(foo.index(max(foo)))
foo.pop(foo.index(max(foo)))
foo = tmp
You can use enumerate to annotate indices on each item, and then use heapq.nlargest to obtain the highest two of the list, after which you extract the indices into a list:
import heapq
from operator import itemgetter
print(list(map(itemgetter(0), heapq.nlargest(2, enumerate(foo), key=itemgetter(1)))))
This outputs: [0, 4]
Another approach would be:
foo = [107,6,34,12,82]
n=2
[i for i, x in sorted(enumerate(foo), key=lambda x: -x[1])[:n]]
#[0, 4]
You can sort tuples of (value, index) and then recover the indices.
def n_highest(lst, n):
ordered = sorted([(value, idx) for idx, value in enumerate(lst)], reverse=True)
return [idx for value, idx in ordered[:n]]
print(n_highest([107, 6, 34, 12, 82], 2))
Output
[0, 4]
You can use the below approach too:
myList=[1,50,30,15,14]
sorted_ind_list=[]
for a in sorted((e,i) for i,e in enumerate(myList)):
sorted_ind_list.append(a[1])
print(sorted_ind_list[0:2])
sort has Better performance over heap.
comparison
I have a function in which:
Given a list of Python values xs and a non-negative integer n, construct and return a copy of xs but with each value replicated n times. Do NOT modify the original list 'xs'
To do this, I created some small code that multiplied the list xs and then sorted it.
def replicate(xs,n):
xscopy = sorted(n * xs)
return xscopy
This code will result in a function input like "replicate([1,2,2,3],2)" to output as [1,1,2,2,2,2,3,3] which is correct. Note how the correct output has the numbers' places matching.
However, when a negative number is in the list, the 'sort' function sees the negative number as smaller than the positive numbers and shifts the position of the negative number from where it was originally on the list.
For example: replicate([1,-1,2,1],2) outputs to [-1,-1,1,1,1,1,2,2] rather than, the correct version, [1,1,-1,-1,2,2,1,1].
Notice how the negative 1 shifted?
I am required to use a loop of some sort (while or for) for this task and I can't figure out how I could incorporate a loop that would both preserve the positions of the numbers and append the numbers properly into a new list (a new list that contains both the original xs, and the duplicates, in the same order as the original.
EDIT: I should add that list comprehension is restricted for this task
You could just use a nested list comprehension if you want to keep the order of the elements from the original list, this will take each element from the list xs, repeat it n times and then go for the next element and so on:
xs = [1,-1,2,1]
n = 2
[x for x in xs for _ in range(n)]
# [1, 1, -1, -1, 2, 2, 1, 1]
xs = [1,2,3]
n = 2
[x for x in xs for _ in range(n)]
# [1, 1, 2, 2, 3, 3]
def replicate(xs, n):
return [item for item in xs for repeat in range(n)]
You don't need to sort it. For each number in the list, loop n times and add it to a new list, so you preserve the original list and still get what you wanted.
def replicate(xs,n):
result = []
for num in xs:
for _ in range(n):
result.append(num)
return result
A cleaner way would be using list comprehension return [num for num in xs for _ in range(n)]
Either way, output of replicate([1,-1,2,1],2) is [1, 1, -1, -1, 2, 2, 1, 1]
You can create a single-element list, and then multiply it. Once you have an n-element list, you can extend() the result list with that portion of the result.
result = []
for item in xs:
nitems = [item] * n
result.extend(nitems)
The functools.reduce function can be used for this:
import functools
return functools.reduce((lambda a,b: a + [b]*n), xs, [])
The sum builtin can do something similar:
return sum( [ [x]*n for x in xs], [])
The itertools.chain function can paste together a bunch of iterables, so you could just multiply the values into a bunch of sub-lists and pass them to it:
import itertools
return list(itertools.chain(*[ [x]*n for x in xs ]))
Or, without the splat (*) operator:
import itertools
return list(itertools.chain.from_iterable([[x]*n for x in xs])
Or, you could zip the original list against itself, then flatten those tuples. (This would be good for long lists, especially if you could just return the iterable instead of a list):
import itertools
return list(itertools.chain.from_iterable(zip(*[iter(xs) for _ in range(n)])))