This question already has answers here:
What is the difference between slice assignment that slices the whole list and direct assignment?
(4 answers)
Closed 6 years ago.
I tried to understand [:] in the beginning, but I can't find any document mention it. Where is the best place to learn advanced grammar for Python? Google search won't find [:]. But I figured it out at the end. I just wonder where is best place to learn Python 'tricks'.
For example:
def test(x, y):
x[:] = y
#x = y
>>> a = [0.5,0.6]
>>> b = [0.3]
>>> test(a, b)
>>>
>>> print a
[0.3] # [0.5,0.6]
x[:] means the entire sequence. It's basically x[from:to].
Omitting from means, from the beginning until the to.
>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numbers[:5]
[0, 1, 2, 3, 4]
Omitting to means, from the from until the end.
>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numbers[5:]
[5, 6, 7, 8, 9]
Omitting both means the entire list.
>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numbers[:]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Setting numbers[:] means setting the entire list:
>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numbers[:] = [1, 2, 3, 4]
>>> numbers
[1, 2, 3, 4]
Keep in mind setting numbers[:] changes that list and does not create a new one. The object will still have the same id().
The term you need to search for is slice. x[start:end:step] is the full form, any one can be omitted to use a default value: start defaults to 0, end defaults to the length of the list, and step defaults to 1. So x[:] means exactly the same as x[0:len(x):1]. You can find more information at
the Expression section of the language reference, and section four of the python tutorial might also be helpful.
The notation x[:] is equivalent to x[0:n] where n is len(x). It specifies the range of elements of x from 0 through n-1 inclusive.
When read, a new list, string, or whatever is created, containing the specified range of elements.
When assigned to, the specified range of elements is destructively replaced in the original. Note that this is allowed for lists, but not for strings.
Related
Say that the target isn't in the list. Instead of outputting -1, I want it to output the rightmost value that's less than the target, followed by the next value (if there is one). In other words, I want to see the gap where the target would be.
If there was a list [1, 2, 4, 7, 7, 8, 9] and I was looking for 5, instead of outputting -1 I want it to output [4, 7]. How do I do that?
Use bisect.bisect to find the insertion point for 5 using binary search, and then take the elements before and after that point:
>>> import bisect
>>> a = [1, 2, 4, 7, 7, 8, 9]
>>> i = bisect.bisect(a, 5)
>>> a[i-1:i+1]
[4, 7]
I was attempting to remove all duplicated numbers in a list.
I was trying to understand what is wrong with my code.
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
for x in numbers:
if numbers.count(x) >= 2:
numbers.remove(x)
print(numbers)
The result I got was:
[1, 1, 6, 5, 2, 3]
I guess the idea is to write code yourself without using library functions. Then I would still suggest to use additional set structure to store your previous items and go only once over your array:
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
unique = set()
for x in numbers:
if x not in unique:
unique.add(x)
numbers = list(unique)
print(numbers)
If you want to use your code then the problem is that you modify collection in for each loop, which is a big NO NO in most programming languages. Although Python allows you to do that, the problem and solution are already described in this answer: How to remove items from a list while iterating?:
Note: There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop. This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence, e.g.,
for x in a[:]:
if x < 0: a.remove(x)
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
Using a shallow copy of the list:
for x in numbers[:]:
if numbers.count(x) >= 2:
numbers.remove(x)
print(numbers) # [1, 6, 5, 2, 3]
Alternatives:
Preserving the order of the list:
Using dict.fromkeys()
print(list(dict.fromkeys(numbers).keys())) # [1, 6, 5, 2, 3]
Using more_itertools.unique_everseen(iterable, key=None):
from more_itertools import unique_everseen
print(list(unique_everseen(numbers))) # [1, 6, 5, 2, 3]
Using pandas.unique:
import pandas as pd
print(pd.unique(numbers).tolist()) # [1, 6, 5, 2, 3]
Using collections.OrderedDict([items]):
from collections import OrderedDict
print(list(OrderedDict.fromkeys(numbers))) # [1, 6, 5, 2, 3]
Using itertools.groupby(iterable[, key]):
from itertools import groupby
print([k for k,_ in groupby(numbers)]) # [1, 6, 5, 2, 3]
Ignoring the order of the list:
Using numpy.unique:
import numpy as np
print(np.unique(numbers).tolist()) # [1, 2, 3, 5, 6]
Using set():
print(list(set(numbers))) # [1, 2, 3, 5, 6]
Using frozenset([iterable]):
print(list(frozenset(numbers))) # [1, 2, 3, 5, 6]
Why don't you simply use a set:
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
numbers = list(set(numbers))
print(numbers)
Before anything, the first advice I can give is to never edit over an array that you are looping. All kinds of wacky stuff happens. Your code is fine (I recommend reading other answers though, there's an easier way to do this with a set, which pretty much handles the duplication thing for you).
Instead of removing number from the array you are looping, just clone the array you are looping in the actual for loop syntax with slicing.
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
for x in numbers[:]:
if numbers.count(x) >= 2:
numbers.remove(x)
print(numbers)
print("Final")
print(numbers)
The answer there is numbers[:], which gives back a clone of the array. Here's the print output:
[1, 1, 1, 6, 5, 5, 2, 3]
[1, 1, 6, 5, 5, 2, 3]
[1, 6, 5, 5, 2, 3]
[1, 6, 5, 5, 2, 3]
[1, 6, 5, 5, 2, 3]
[1, 6, 5, 2, 3]
[1, 6, 5, 2, 3]
[1, 6, 5, 2, 3]
[1, 6, 5, 2, 3]
Final
[1, 6, 5, 2, 3]
Leaving a placeholder here until I figure out how to explain why in your particular case it's not working, like the actual step by step reason.
Another way to solve this making use of the beautiful language that is Python, is through list comprehension and sets.
Why a set. Because the definition of this data structure is that the elements are unique, so even if you try to put in multiple elements that are the same, they won't appear as repeated in the set. Cool, right?
List comprehension is some syntax sugar for looping in one line, get used to it with Python, you'll either use it a lot, or see it a lot :)
So with list comprehension you will iterate an iterable and return that item. In the code below, x represents each number in numbers, x is returned to be part of the set. Because the set handles duplicates...voila, your code is done.
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
nubmers_a_set = {x for x in numbers }
print(nubmers_a_set)
This seems like homework but here is a possible solution:
import numpy as np
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
filtered = list(np.unique(numbers))
print(filtered)
#[1, 2, 3, 5, 6]
This solution does not preserve the ordering. If you need also the ordering use:
filtered_with_order = list(dict.fromkeys(numbers))
Why don't you use fromkeys?
numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
numbers = list(dict.fromkeys(numbers))
Output: [1,6,5,2,3]
The flow is as follows.
Now the list is [1, 1, 1, 1, 6, 5, 5, 2, 3] and Index is 0.
The x is 1. The numbers.count(1) is 4 and thus the 1 at index 0 is removed.
Now the numbers list becomes [1, 1, 1, 6, 5, 5, 2, 3] but the Index will +1 and becomes 1.
The x is 1. The numbers.count(1) is 3 and thus the 1 and index 1 is removed.
Now the numbers list becomes [1, 1, 6, 5, 5, 2, 3] but the Index will +1 and becomes 2.
The x will be 6.
etc...
So that's why there are two 1's.
Please correct me if I am wrong. Thanks!
A fancy method is to use collections.Counter:
>>> from collections import Counter
>>> numbers = [1, 1, 1, 1, 6, 5, 5, 2, 3]
>>> c = Counter(numbers)
>>> list(c.keys())
[1, 6, 5, 2, 3]
This method have a linear time complexity (O(n)) and uses a really performant library.
You can try:
from more_itertools import unique_everseen
items = [1, 1, 1, 1, 6, 5, 5, 2, 3]
list(unique_everseen(items))
or
from collections import OrderedDict
>>> items = [1, 1, 1, 1, 6, 5, 5, 2, 3]
>>> list(OrderedDict.fromkeys(items))
[1, 2, 0, 3]
more you can find here
How do you remove duplicates from a list whilst preserving order?
This question already has an answer here:
Create index list for np.split from the list that already has number for each section
(1 answer)
Closed 3 years ago.
Let's say I've got an array [0, 1, 2, 3, 4, 5, 6, 7] and a tuple: (3, 3, 2).
I'm looking for a way to split my array to 3 array based on my tuple data:
[0, 1, 2]
[3, 4, 5]
[6, 7]
I can write a simple code like this to get what I want, however I'm looking for a correct and pythonic way to do this:
I used lists for simplicity.
a = [0, 1, 2, 3, 4, 5, 6, 7]
b = (3, 3, 2)
pointer = 0
for i in b:
lst = []
for j in range(i):
lst.append(a[pointer])
pointer += 1
print(lst)
Or this one:
a = [0, 1, 2, 3, 4, 5, 6, 7]
b = (3, 3, 2)
pointer = 0
for i in b:
lst = a[pointer:pointer+i]
pointer += i
print(lst)
Results:
[0, 1, 2]
[3, 4, 5]
[6, 7]
you can use the split method of numpy
import numpy as np
a = [0, 1, 2, 3, 4, 5, 6, 7]
b = (3, 3, 2)
c = np.split(a, np.cumsum(b)[:-1])
for r in c:
print(r)
np.split(a, b) splits a by the indices in b along a given axis(0 by default).
If you don't want to modify your input list, you can use an iterator and the itertools module.
>>> from itertools import islice
>>> a = [0, 1, 2, 3, 4, 5, 6, 7]
>>> b = (3, 3, 2)
>>> i = iter(a)
>>> [list(islice(i, x)) for x in b]
[[0, 1, 2], [3, 4, 5], [6, 7]]
In the first step you create an iterator, which starts at the first element of a. Then you iterate in a list comprehension over your numbers in b and in each step you pull accordingly many elements from the iterator and store them in your result list.
One simpler way is this:
a = [0, 1, 2, 3, 4, 5, 6, 7]
b = (3, 3, 2)
for ind in b:
print(a[:ind])
a = a[ind:]
It loops through slice sizes in b while shortening the original array every time. You can easily append the resulting slices as sublists if you need them for something else. It's almost like one of your solutions except it doesn't use any extra variables and iterates directly through elements of b.
Also, I wouldn't call variables a and b - surely not in this case where variables have clear meanings that you can express through their names. More meaningful names lessen bugs number and make code more clear, becomes a real difference with larger/more complex code. I'd call a at least in_list and b slices, but with more context this could be better.
The most "concise" syntax would be :
ex_array = [0, 1, 2, 3, 4, 5, 6, 7]
extuple = (3, 3, 2)
result = [ex_array[sum(extuple[:iii]):sum(extuple[:iii])+extuple[iii]] for iii in range(len(extuple))]
result would be a list of the expected sub-lists
Re-using the pairwise function from Compare two adjacent elements in same list, you could also:
from itertools import accumulate
from more_itertools import pairwise
a = [0, 1, 2, 3, 4, 5, 6, 7]
b = (3, 3, 2)
[a[slice(*s)] for s in pairwise(accumulate((0,)+b))]
That begin said, the np.split answer is probably faster (and easier to read).
Have a list arr = [1,3,4,5,2,3,4,2,5,7,3,8,1,9,6,2,1,2,1,3,4,3,4,6,9]
want to remove the duplicate values so that the original list should contains single instances of all elements. Do not want to create a extra list and append the elements from list. Also do not want to use inbuilt "set".
Tried to do that with some code as below:
l = len(arr)
for x in range(l):
for y in range(x+1,l):
if arr[x] == arr[y]:
del arr[y]
Tried the above code and its throwing error
"IndexError: list index out of range"
What I understand is whiling deleting the value the size of the list is changing for which its throwing the error. So I made the below changes. But still its failing with same error:
l = len(arr)
for x in range(l):
for y in range(x+1,l):
if arr[x] == arr[y]:
t = y
del arr[y]
y = t - 1
Can some one help me out on this?
Thanks in Advance.
You are trying to make the code more efficient by caching the length of the list in the local variable l. However, that is not helpful because the list is being trimmed inside the loop, and you are not keeping the cached length variable in sync.
for index in range(len(arr)-1,0,-1):
if arr[index] in arr[:index]:
del arr[index]
By going backwards through the array and looking for earlier occurrences of each element, you can avoid having to worry about the length of the list changing all the time.
This method also preserves the order in which elements occur in the original array. Note the instruction is to only remove duplicates (a.k.a. subsequent occurrences).
For example the list [9,3,4,3,5] should reduce to [9,3,4, 5] as the second occurrence of 3 is considered a duplicate and should be removed.
How about this approach:
>>> set(arr)
set([1, 2, 3, 4, 5, 6, 7, 8, 9]) #Just to compare it with the results below.
>>> arr = [1,3,4,5,2,3,4,2,5,7,3,8,1,9,6,2,1,2,1,3,4,3,4,6,9]
>>> arr.sort()
>>> arr
[1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 7, 8, 9, 9]
>>> for i in arr:
while arr.count(i) > 1:
del arr[i]
>>> arr
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Another approach is to find, after sorting your list, the length of the sublist to delete for each number:
>>> arr = [1,3,4,5,2,3,4,2,5,7,3,8,1,9,6,2,1,2,1,3,4,3,4,6,9]
>>> arr.sort()
>>> arr
[1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 7, 8, 9, 9]
>>> for i,j in enumerate(arr):
del arr[i+1:i+arr.count(j)]
>>> arr
[1, 2, 3, 4, 5, 6, 7, 8, 9]
If I am given a list of numbers and I want to swap one of them with the next two numbers.
Is there a way to do this in one shot, without swapping the first number twice?
To be more specific, let's say I have the following swap function:
def swap_number(list, index):
'''Swap a number at the given index with the number that follows it.
Precondition: the position of the number being asked to swap cannot be the last
or the second last'''
if index != ((len(list) - 2) and (len(list) - 1)):
temp = list[index]
list[index] = list[index+1]
list[index+1] = temp
Now, how do I use this function to swap a number with the next two numbers, without calling swap on the number twice.
For example: I have the following list: list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Now, how do I swap 3 with the 4 and 5 in one shot?
The expected output would be
list = [0, 1, 2, 4, 5, 3, 6, 7, 8, 9]
Something like this?
def swap(lis, ind):
lis.insert(ind+2, lis.pop(ind)) #in-place operation, returns `None`
return lis
>>> lis = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lis = swap(lis, 3)
>>> lis
[0, 1, 2, 4, 5, 3, 6, 7, 8, 9]