Compare all elements of a list - python

To illustrate my problem, imagine I have a list and I want to compare each element with the next one to check if they are the same value. The problem is that when I try to access the last element of the list and compare it with "the next one", that one is out of range, so I would get an error. So, to avoid this, I put a condition when accessing that last element, so I avoid the comparison.
list = [1, 2, 1, 1, 5, 6, 1,1]
for i in range(len(list)):
if i == len(list)-1:
print('Last element. Avoid comparison')
else:
if list[i] == list[i+1]:
print('Repeated')
I guess that there should be a more efficient way to do this. For instance, I was trying to set the condition in the definition of the for loop, something like this:
for i in range(len(list)) and i < len(list)-1
But that is invalid. Any suggestion about how to do this in a more efficient/elegant way?

If you need to start from 0, you should use:
for i in range(len(list) - 1):
if list[i] == list[i + 1]:
print('Repeated')
The parameter stop of range function is just integer, so you can use value len(list) - 1 instead of len(list) to stop iterating on last but one element.

Other answers have solved this, but I think it's worth mentioning an approach that may be closer to idiomatic Python. Python provides iterable unpacking and other tools like the zip function to avoid accessing elements of sequences by index.
# Better to avoid shadowing the build-in name `list`
a_list = [1, 2, 1, 1, 5, 6, 1, 1]
for value, following_value in zip(a_list, a_list[1:]):
if value == following_value:
print("Repeated!")

You can utilize the functionality of range as follows:
for i in range(1, len(list)):
if list[i-1] == list[i]:
print('Repeated')
In this way, you won't overrun the list.

start from one and look backwards
for i in range(1, len(list)):
if list[i-1] == list[i]:
print('Repeated')

This works!
list = [1, 2, 1, 1, 5, 6, 1, 1]
for i in range(len(list)):
if i+1 < len(list) and list[i] == list[i+1]:
print('Repeated')

len(list) is 8
range(len(list)) is 0, 1, ..., 7
but you want the for loop to skip when the index is 6 right?
so given that case ... if i == len(list)-1: this condition will be True when the index is 7 (not the index that you want)
Just change that to if i == len(list)-2:

There are many ways to do this. The most common one is to use zip to pair each item with its successor:
if any(item == successor for item,successor in zip(lst,lst[1:])):
print('repeated')
groupby from itertools is also a popular choice (but not optimal for this):
if any(duplicate for _,(_,*duplicate) in itertools.groupby(lst)):
print('repeated')
A for-loop would only need to track the previous value (no need for indexing):
prev = object() # non-matching initial value
for x in lst:
if prev==x: # compare to previous
print('repeated')
break
prev = x # track previous for next iteration
Iterators can be interesting when traversing data in parallel (here the elements and their predecessors):
predecessor = iter(lst) # iterate over items from first
for x in lst[1:]: # iterate from 2nd item
if x == next(predecessor): # compare to corresponding predecessor
print('repeated')
break

list = [1, 2, 1, 1, 5, 6, 1,1]
for i in range(len(list)):
if list[i] in list[i+1:i+2]:
print('repeated')

If you use only numbers in your list, you might want to work with numpy
for instance:
import numpy as np
np_arr = np.array(lst) # don't use 'list' for your object name.
diffs = np.diff(np_arr)
diffs_indices = np.where(diffs != 0)[0]
It is unclear what your exact uses, but for example in my code, you will get:
>>> diffs_indexes
array([0, 1, 3, 4, 5])
Which are the indices where elelment[i] != element[i+1]

Related

Insertion sort algorithm doesn't bother about last number in list

I'd like to write a insertion sort algorithm. I've almost finished it, but the function itself seems to not even bothering about last number. What could be wrong here?
def insertion_sort(user_list):
sorted_list = []
sorted_list.append(user_list[0])
for index in range(0, len(user_list)):
if user_list[index] < user_list[-1]:
for reversed_index in range(index, 0, -1):
if user_list[reversed_index] < user_list[reversed_index-1]:
user_list[reversed_index], user_list[reversed_index-1] = user_list[reversed_index-1], user_list[reversed_index]
print(user_list)
print("\n\n", user_list)
if __name__ == '__main__':
user_list = [4, 3, 2, 10, 12, 1, 5, 6]
insertion_sort(user_list)
replace if user_list[index] < user_list[-1] with if user_list[index] < user_list[index-1].
While doing insertion sort, we compare two consecutive elements to check if they are out of order. Comparing with the last element of user_list seems like a typo.

Updating the range of list in function argument

Problem to solve: Define a Python function remdup(l) that takes a non-empty list of integers l
and removes all duplicates in l, keeping only the last occurrence of each number. For instance:
if we pass this argument then remdup([3,1,3,5]) it should give us a result [1,3,5]
def remdup(l):
for last in reversed(l):
pos=l.index(last)
for search in reversed(l[pos]):
if search==last:
l.remove(search)
print(l)
remdup([3,5,7,5,3,7,10])
# intended output [5, 3, 7, 10]
On line 4 for loop I want the reverse function to check for each number excluding index[last] but if I use the way I did in the above code it takes the value at pos, not the index number. How can I solve this
You need to reverse the entire slice, not merely one element:
for search in reversed(l[:pos]):
Note that you will likely run into a problem for modifying a list while iterating. See here
It took me a few minutes to figure out the clunky logic. Instead, you need the rest of the list:
for search in reversed(l[pos+1:]):
Output:
[5, 3, 7, 10]
Your original algorithm could be improved. The nested loop leads to some unnecessary complexity.
Alternatively, you can do this:
def remdup(l):
seen = set()
for i in reversed(l):
if i in seen:
l.remove(i)
else:
seen.add(i)
print(l)
I use the 'seen' set to keep track of the numbers that have already appeared.
However, this would be more efficient:
def remdup(l):
seen = set()
for i in range(len(l)-1, -1, -1):
if l[i] in seen:
del l[i]
else:
seen.add(l[i])
print(l)
In the second algorithm, we are iterating over the list in reverse order using a range, and then we delete any item that already exists in 'seen'. I'm not sure what the implementation of reversed() and remove() is, so I can't say what the exact impact on time/space complexity is. However, it is clear to see exactly what is happening in the second algorithm, so I would say that it is a safer option.
This is a fairly inefficient way of accomplishing this:
def remdup(l):
i = 0
while i < len(l):
v = l[i]
scan = i + 1
while scan < len(l):
if l[scan] == v:
l.remove(v)
scan -= 1
i -= 1
scan += 1
i += 1
l = [3,5,7,5,3,7,10]
remdup(l)
print(l)
It essentially walks through the list (indexed by i). For each element, it scans forward in the list for a match, and for each match it finds, it removes the original element. Since removing an element shifts the indices, it adjusts both its indices accordingly before continuing.
It takes advantage of the built-in the list.remove: "Remove the first item from the list whose value is equal to x."
Here is another solution, iterating backward and popping the index of a previously encountered item:
def remdup(l):
visited= []
for i in range(len(l)-1, -1, -1):
if l[i] in visited:
l.pop(i)
else:
visited.append(l[i])
print(l)
remdup([3,5,7,5,3,7,10])
#[5, 3, 7, 10]
Using dictionary:
def remdup(ar):
d = {}
for i, v in enumerate(ar):
d[v] = i
return [pair[0] for pair in sorted(d.items(), key=lambda x: x[1])]
if __name__ == "__main__":
test_case = [3, 1, 3, 5]
output = remdup(test_case)
expected_output = [1, 3, 5]
assert output == expected_output, f"Error in {test_case}"
test_case = [3, 5, 7, 5, 3, 7, 10]
output = remdup(test_case)
expected_output = [5, 3, 7, 10]
assert output == expected_output, f"Error in {test_case}"
Explanation
Keep the last index of each occurrence of the numbers in a dictionary. So, we store like: dict[number] = last_occurrence
Sort the dictionary by values and use list comprehension to make a new list from the keys of the dictionary.
Along with other right answers, here's one more.
from iteration_utilities import unique_everseen,duplicates
import numpy as np
list1=[3,5,7,5,3,7,10]
dup=np.sort(list((duplicates(list1))))
list2=list1.copy()
for j,i in enumerate(list2):
try:
if dup[j]==i:
list1.remove(dup[j])
except:
break
print(list1)
How about this one-liner: (convert to a function is easy enough for an exercise)
# - one-liner Version
lst = [3,5,7,5,3,7,10]
>>>list(dict.fromkeys(reversed(lst)))[::-1]
# [5, 3, 7, 10]
if you don't want a new list, you can do this instead:
lst[:] = list(dict.fromkeys(reversed(lst)))[::-1]

Iterating through list to find max and swapping it to front

The question exactly is: Create a function (jumpMaximum) that, given any list of integers list, returns a list with the same elements as list,
except that the first element has been swapped with the maximum element in list.
Note: This function should not print the list, but return it.
My code so far is:
def jumpMaximum (list):
maximum= list[0]
for i in range(len(list)):
for j in range(len(list)):
if i>j and i>maximum:
maximum=i
maximum, list[0] = list[0], maximum
return list
print(jumpMaximum([1,2,3,4]))
Now when I run this, I get [3, 2, 3, 4], but this is wrong of course. I made the for loops to iterate through the list and find the maximum. And then I wrote //maximum, list[0] = list[0], maximum// to swap the first element with the max, so I am not sure where it is going wrong.
You could do something like this:
def jump_maximum(lst):
_, i = max((e, i) for i, e in enumerate(lst))
lst[0], lst[i] = lst[i], lst[0]
return lst
print(jump_maximum([1, 2, 3, 4]))
Output
[4, 2, 3, 1]
The idea is to use enumerate in conjunction with max to find the index (i) of the maximum element, then simply swap the elements and return the list.
As a side note you should not use names such as list for your variables because it shadows the built-in list.
UPDATE
If you cannot use enumerate or max, you can do it like this:
def jump_maximum(lst):
i, ma = 0, lst[0]
for j in range(len(lst)):
if lst[j] > ma:
ma = lst[j]
i = j
lst[0], lst[i] = lst[i], lst[0]
return lst
print(jump_maximum([1, 2, 3, 4]))
Output
[4, 2, 3, 1]
The idea is to substitute enumerate and max by the for loop. Also you don't need the nested loops.

Generate list with every nth value from input

I had the following code:
return [p.to_dict() for p in points]
I changed it to only print every nth row:
n = 100
count = 0
output = []
for p in points:
if (count % n == 0):
output.append(p.to_dict())
count += 1
return output
Is there a more pythonic way to write this, to acheive the same result?
use enumerate and modulo on the index in a modified list comprehension to filter the ones dividable by n:
return [p.to_dict() for i,p in enumerate(points) if i % n == 0]
List comprehension filtering is good, but in that case, eduffy answer which suggests to use slicing with a step is better since the indices are directly computed. Use the filter part only when you cannot predict the indices.
Improving this answer even more: It's even better to use itertools.islice so not temporary list is generated:
import itertools
return [p.to_dict() for p in itertools.islice(points,None,None,n)]
itertools.islice(points,None,None,n) is equivalent to points[::n] but performing lazy evaluation.
The list slicing syntax takes an optional third argument to define the "step". This take every 3rd in a list:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(10)[::3]
[0, 3, 6, 9]
you can use enumerate with list comprehension.
[p.to_dict() for i, p in enumerate(points) if i %100 == 0]

python: list assignment index out of range

for row in c:
for i in range(len(row)):
if i not in keep:
del row[i]
i am getting this error on the last line:
IndexError: list assignment index out of range
i dont understand how it can be out of range if it exists! please help
If row is a list, then don't forget that deleting an element of a list will move all following elements back one place to fill the gap, so all of the later indices into the list will be off-by-one (and each time you delete another element, the error in each index grows by one). There are a few ways to avoid this problem -- to give one, try iterating through the list backwards.
To respond to how to iterate backwards, you could try using the extra parameters you can pass to range. The first two parameters give the range to iterate over (the lower bound being inclusive, and the upper bound exclusive), and the third parameter is the step:
>>> range(5)
[0, 1, 2, 3, 4]
>>> range(0, 5)
[0, 1, 2, 3, 4]
>>> range(3, 5)
[3, 4]
>>> range(3, 5, -1)
[]
>>> range(5, 3, -1)
[5, 4]
So, in your case, it seems you'd want:
range(len(row) - 1, -1, -1)
Or the easier to read (thanks to viraptor):
reversed(range(len(row))
Alternatively, you could try using list comprehensions (I'm assuming c is a list):
for row_number, row in enumerate(c):
c[row_number] = [x for i, x in enumerate(row) if i in keep]
Maybe you can write it like this
for row in c:
row[:] = [x for i,x in enumerate(row) if i in keep]
You should not change a list while you are iterating over it to prevent such errors.
The index existed at the start of the loop, but once you have deleted an element, the list is shorter, and does not contain the same number of elements. Thus, your indices may be wrong. If you delete from the end of the list forward, that won't invalidate list indices.
But I'm surprised about this pattern; perhaps there is a better way to solve what you're looking for? Maybe set subtraction or something similar would do what you want to do without modifying a list dozens or hundreds of times.
A more pythonic solution would be to use filter():
>>> keep = {1,3,5}
>>> row = [1, 2, 3, 4]
>>> list(filter(keep.__contains__, row))
[1, 3]
As a filtering function we can use keep.__contains__ which corresponds to the in operator of the set. Because of this we only get items from the row which are in keep.
Note: Use row[:] = filter(keep.__contains__, row) for in-place update of row or a list comprehension: c = [filter(keep.__contains__, row) for row in c]

Categories

Resources