Bring equal list elements together - python

Is this an alright way to bring equal elements together (make them appear consecutively in the list)?
>>> a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
>>> for x in reversed(a):
a.remove(x)
a.append(x)
>>> a
[8, 8, 8, 8, 2, 2, 2, 1, 1, 7]
Edit: List where it allegedly doesn't work (see comments):
>>> a = [2, 7, 1, 1, 8, 2, 8, 1, 8, 2, 8]
>>> for x in reversed(a):
a.remove(x)
a.append(x)
>>> a
[8, 8, 8, 8, 2, 2, 2, 1, 1, 1, 7]

Just use list.sort:
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
a.sort()
print(a)
Output:
[1, 1, 2, 2, 2, 7, 8, 8, 8, 8]
If u want it in descending order, then pass reverse = True to list.sort:
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
a.sort(reverse = True)
print(a)
Output:
[8, 8, 8, 8, 7, 2, 2, 2, 1, 1]

Another solution, giving no guarantee on the order of the output, but that is O(n) instead O(n*log(n)) for the solution using sort: count the values, and create a new list with the corresponding counts for each value:
from collections import Counter
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
counts = Counter(a)
out = []
for value, count in counts.items():
out.extend([value]*count)
print(out)
# [2, 2, 2, 7, 1, 1, 8, 8, 8, 8]
As suggested by #Manuel, there is a Counter method that I had never noticed, Counter.elements():
Return an iterator over elements repeating each as many times as its count. Elements are returned in the order first encountered
So, to get an output in original order, and in O(n), the code would be simply:
from collections import Counter
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
out = list(Counter(a).elements())
print(out)
# [2, 2, 2, 7, 1, 1, 8, 8, 8, 8]

Why/how it works, and about this possibly being an undefined implementation detail:
The iterator starts at the end:
iterator
↓
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
x = undefined
Then the for loop asks the iterator for an item. After getting the last 8, the iterator decreases its index into the list and returns the 8 (code), which the for loop stores in x. So now we have:
↓
a = [2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
x = 8
Then a.remove(x) removes the first 8, which shifts all later items to the left:
↓
a = [2, 7, 1, 2, 8, 1, 8, 2, 8]
x = 8
And a.append(x) appends it at the end:
↓
a = [2, 7, 1, 2, 8, 1, 8, 2, 8, 8]
x = 8
Then the for loop gets the next item from the iterator, which is the same 8 as before, only at the lower index:
↓
a = [2, 7, 1, 2, 8, 1, 8, 2, 8, 8]
x = 8 (same one again)
The remove again removes the first 8 (which was originally the second 8):
↓
a = [2, 7, 1, 2, 1, 8, 2, 8, 8]
And it gets appended:
↓
a = [2, 7, 1, 2, 1, 8, 2, 8, 8, 8]
The next round moves the originally third 8 to the end:
↓
a = [2, 7, 1, 2, 1, 2, 8, 8, 8, 8]
And then finally the originally fourth 8 (which we've been finding over and over again) moves as well:
↓
a = [2, 7, 1, 2, 1, 2, 8, 8, 8, 8]
The same then happens to the 2's, 1's and the 7, so we end up with:
a = [8, 8, 8, 8, 2, 2, 2, 1, 1, 7]
JBernardo commented that it "could be undefined behavior on a different python implementation". I guess it could be, but I'd blame that implementation. The Python reference documentation notes (although about the forward iterator):
There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, e.g. 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.
And that's not marked as a CPython implementation detail, which the Python documentation does at plenty of other places: 261 results for googling site:docs.python.org/3/ "CPython implementation detail"

Related

Compare two lists and get the indices where the values are different

I would like help with the following situation
I have two lists:
Situation 1:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 1, 2, 3, 5, 5, 6, 7, 8, 9]
I need key output: Key 4 is different
Situation 2:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
I need key output: false -> no key is different
Situation 3:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 9, 2, 3, 4, 5, 6, 7, 3, 9]
I need key output: Key 1 and Key 8 is different
How could I resolve this? My array has 260 keys
You can use a list comprehension with zip, and enumerate to get the indices. Use short-circuiting and the fact that an empty list is falsy to get False:
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 1, 2, 3, 5, 5, 6, 7, 8, 9]
out = [i for i,(e1,e2) in enumerate(zip(a,b)) if e1!=e2] or False
output:
[4]
output for example #2: False
output for example #3: [1, 8]
An approach with itertools.compress. Compare the lists to check difference in values, pass the result of the comparison to compress which will pick-up only the True ones.
from itertools import compress
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = [0, 1, 2, 3, 5, 5, 6, 7, 8, 9]
result = tuple(compress(a, map(int.__ne__, a, b)))
if not result:
print(False)
else:
print(result)
The current answer is really good, but for an approach which doesn't need zip/enumerate, loop like this:
lista = []
listb = []
unequal_keys = []
for idx in range(len(lista)):
if lista[idx] != listb[idx]:
unequal_keys.append(idx)
if unequal_keys == []:
print(False)
else:
print(unequal_keys)
I recommend learning new techniques and using build ins like zip and enumerate though. They are much more pythonic and faster!

Creating duplicates in a python list

I want to repeat numbers in a list but I'm not sure how to start. Here's an example of what I'm looking to do
list1=[2,4,5,1,7,8,2,9]
list_I_want=[2,2,4,4,5,5,1,1,7,7,8,8,2,2,9,9]
I was thinking probably a for loop to do this but I am not sure where to start
Here's an easy method using nested loops in a list comprehension:
>>> list1=[2,4,5,1,7,8,2,9]
>>> [i for i in list1 for _ in range(2)]
[2, 2, 4, 4, 5, 5, 1, 1, 7, 7, 8, 8, 2, 2, 9, 9]
This is equivalent to doing nested for loops and appending in the inner loop:
>>> list_i_want = []
>>> for i in list1:
... for _ in range(2):
... list_i_want.append(i)
...
>>> list_i_want
[2, 2, 4, 4, 5, 5, 1, 1, 7, 7, 8, 8, 2, 2, 9, 9]
If you want to do it in-place (i.e. without creating a separate list), you can insert each number at its position in a loop. However, the list indexes will change as you insert new items so, performing the insertions backwards will ensure that the remaining items to process are at their expected positions:
list1=[2,4,5,1,7,8,2,9]
for i in reversed(range(len(list1))):
list1.insert(i,list1[i])
print(list1)
[2, 2, 4, 4, 5, 5, 1, 1, 7, 7, 8, 8, 2, 2, 9, 9]

Need help understanding bubble sort solution code

So, I was learning about Bubble Sort and saw this code:
def bubblesort(list_a):
indexing_length = len(list_a) - 1
sorted = False
while not sorted:
sorted = True
for i in range(0, indexing_length):
if list_a[i] > list_a[i+1]:
sorted = False
list_a[i], list_a[i+1] = list_a[i+1], list_a[i] # flip position
return list_a
I tried running this code on my notebook so I could understand this better. I tried 5, 2, 3, 7, 10, 1, 8 and the process was like this:
--> 2, 5, 3, 7, 10, 1, 8
--> 2, 3, 5, 7, 10, 1, 8
--> 2, 3, 5, 7, 1, 10, 8
--> 2, 3, 5, 7, 1, 8, 10
I ended up with unsorted array because I thought for loop does only one iteration. Am I understanding something wrong? Could anyone explain it to me little bit easier please?
Bubble Sort is the simplest sorting algorithm that works by repeatedly swapping the adjacent elements if they are in wrong order. This sorting algorithm also known as Brute Force Approach, the reason is that during sorting each element of list will compare with the every other element of the same list and if the compared number are not in order so it will swap the position. Lets have a look on your example list [5, 2, 3, 7, 10, 1, 8].
1st pass: compare 0th index(5) and 1st index(2) and compare it
if 0th index(5) > 1st index(2) then it will swap the position else no swap counter will increase. [5, 2, 3, 7, 10, 1, 8] condition True(5 > 2), SWAP [2, 5, 3, 7, 10, 1, 8], then again compare [2, 5, 3, 7, 10, 1, 8] condition True(5 > 3), SWAP [2, 3, 5, 7, 10, 1, 8], and so on.
[2, 3, 5, 7, 10, 1, 8] True(10>1).
[2, 3, 5, 7, 1, 10, 8] True(10>8).
[2, 3, 5, 7, 1, 8, 10]
2nd pass:
[2, 3, 5, 7, 1, 8, 10]
[2, 3, 5, 7, 1, 8, 10]
[2, 3, 5, 7, 1, 8, 10] True(7>1).
[2, 3, 5, 1, 7, 8, 10]
[2, 3, 5, 1, 7, 8, 10]
3rd pass:
[2, 3, 5, 1, 7, 8, 10]
[2, 3, 5, 1, 7, 8, 10] True(5>1).
[2, 3, 1, 5, 7, 8, 10]
[2, 3, 1, 5, 7, 8, 10]
4th pass:
[2, 3, 1, 5, 7, 8, 10] True(3>1).
[2, 1, 3, 5, 7, 8, 10]
[2, 1, 3, 5, 7, 8, 10]
5th pass:
[2, 1, 3, 5, 7, 8, 10] True(2>1).
[1, 2, 3, 5, 7, 8, 10]
[1, 2, 3, 5, 7, 8, 10]
6th pass:
[1, 2, 3, 5, 7, 8, 10]
7th pass:
[1, 2, 3, 5, 7, 8, 10]
def bubbleSort(arr):
n = len(arr)
for i in range(n):
print(arr[i])
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
print(arr[j], arr[j+1])
arr[j], arr[j + 1] = arr[j + 1], arr[j]
print(arr)
arr = [5, 2, 3, 7, 10, 1, 8]
bubbleSort(arr)
print("Sorted array")
print(arr)
The list will be [2, 3, 5, 7, 1, 8, 10] after the for loop completes the first time, but the value of sorted will be False. Since you're still inside the while not sorted loop, everything inside that loop will run again, including another for loop starting at index 0.
This will keep going until the list is sorted when the for loop completes, since that will make the while loop's condition False.

Printing a list after having removed adjacent duplicate elements

new_list = []
i = 0
def remove_adjacent(nums):
global i
while i < len(nums) - 1:
if nums[i] != nums[i+1]:
new_list.append(nums[i])
else:
i += 1
remove_adjacent(nums[i:])
return
i += 1
l = [1, 2, 3, 5, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 8, 8]
remove_adjacent(l)
print new_list
Question: Given a list of numbers, return a list where all adjacent == elements have been reduced to a single element, so [1, 2, 2, 3] returns [1, 2, 3]. You may create a new list or modify the passed in list.
Issue: The final list printed consists of [1, 2, 3, 5] instead of [1, 2, 3, 5, 4, 5, 6, 7, 8, 9, 8]
What you would want is a problem best solved by itertools.groupby
l
Out[35]: [1, 2, 3, 5, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 8, 8]
from itertools import groupby
[k for k, _ in groupby(l)]
Out[36]: [1, 2, 3, 5, 4, 5, 6, 7, 8, 9, 8]
What itertools.groupby does is, it groups consecutive keys together by generating a tuple of the element and the consecutive group as a list
To get a clear understanding of itertools.groupby, you can dump the resultant list of tuples generated by grouping the list of consecutive numbers
[(k, list(g)) for k, g in groupby(l)]
Out[40]:
[(1, [1]),
(2, [2]),
(3, [3]),
(5, [5]),
(4, [4, 4]),
(5, [5, 5]),
(6, [6]),
(7, [7, 7, 7]),
(8, [8, 8]),
(9, [9]),
(8, [8, 8])]
new_list = []
def remove_adjacent(nums):
i = 0
while i < len(nums) - 1:
if nums[i] != nums[i+1]:
new_list.append(nums[i])
else:
i += 1
remove_adjacent(nums[i:])
return
i += 1
l = [1, 2, 3, 5, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 8, 8]
remove_adjacent(l)
# appending the last item
new_list.append(l[len(l)-1])
print (new_list.append(nums[len(nums) - 1]))
Output
[1, 2, 3, 5, 4, 5, 6, 7, 8, 9, 8]
This is perfect for a generator. I'm not altering the original list. Instead, I'm returning a new list with no adjacent values equal to one another.
def removerator(l):
last = None
for x in l:
if x != last:
last = x
yield x
list(removerator(l))
[1, 2, 3, 5, 4, 5, 6, 7, 8, 9, 8]
Setup
l = [1, 2, 3, 5, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 8, 8]
I made a function where it get a list and iterate over its items, then it adds the items that aren't the same as the items already added to the list in the previous iteration.
l = [1, 2, 3, 5, 4, 4, 5, 5, 6, 7, 7, 7, 8, 8, 9, 8, 8] # List that we want to change
def remove_adjacent(l): # Define a new function and accept an argument: the list to check.
new = [l[0]] # Make a new list (temporal) and assing like it's first item the first item of the main list. It's the same as new = [] and new.append(l[0]).
for item in l[1:]: # We iterate across the list, but we don't iterate on the first item because we've alreaday added it to the list, if you want you can delete the slice in [1:] since it will only make the iteration a really small fraction more slowly.
if new[-1] != item: # We check if the new item is the same as the last item added to the new list, if not, we add it.
new.append(item) # We add the item to the new list.
return new # We return the list.
print(remove_adjacent(l)) # We check it.
# [1, 2, 3, 5, 4, 5, 6, 7, 8, 9, 8]

Duplicate problems with Selection Sort

If I have a list where all values are unique, the code runs fine. However, if there is a duplicate value within the list, when finding the minimum value for the next iteration, it pulls from the entire list rather than just the remainder of the list.
for n in range(0,len(lst)):
a = min(lst[n:]) #minimum value within remainder of set
i = lst.index(a) #index value for minimum value within remainder of set
temp = lst[n]
lst[n] = a
lst[i] = temp
Results look like this:
lst = [6, 8, 9, 1, 3, 4, 7, 5, 4]
[1, 8, 9, 6, 3, 4, 7, 5, 4]
[1, 3, 9, 6, 8, 4, 7, 5, 4]
[1, 3, 4, 6, 8, 9, 7, 5, 4]
[1, 3, 6, 4, 8, 9, 7, 5, 4]
[1, 3, 6, 8, 4, 9, 7, 5, 4]
[1, 3, 6, 8, 9, 4, 7, 5, 4]
[1, 3, 6, 8, 9, 7, 4, 5, 4]
[1, 3, 6, 8, 9, 7, 5, 4, 4]
[1, 3, 6, 8, 9, 7, 5, 4, 4]
I'm looking for it to return this:
[1, 3, 4, 4, 5, 6, 7, 8, 9]
When n is 4, the next minimum is 4 again, but lst.index() finds the first 4 at position 3 instead.
Start searching for the miminum from n; the .index() method takes a second argument start, from where to start searching:
i = lst.index(a, n)
Note that Python can assign to two targets in place, no need to use a temporary intermediate. range() with just one argument starts from 0:
for n in range(len(lst)):
a = min(lst[n:])
i = lst.index(a, n)
lst[n], lst[i] = a, lst[n]
Demo:
>>> lst = [6, 8, 9, 1, 3, 4, 7, 5, 4]
>>> for n in range(0,len(lst)):
... a = min(lst[n:])
... i = lst.index(a, n)
... lst[n], lst[i] = a, lst[n]
...
>>> lst
[1, 3, 4, 4, 5, 6, 7, 8, 9]

Categories