Weird behaviour of a generator function while converting it to a list

Question:

I am trying to get every state of a list while it is being sorted for a visualization.
So with bubbleSort algorithm

i made a generator function :

def bubbleSort(arr): 

    n = len(arr)

    yield arr  # yielding original state

    for i in range(n):

        swapped = False
        
        for j in range(0, n-i-1): 
        
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                swapped = True

                yield arr   # yielding every state during bubbleSort
                
        if (swapped == False):
            break

arr = [64, 34, 25, 12, 22, 11, 90]

generator=bubbleSort(arr)

for i in generator:
    print(i)

Now this gives me my desired output.

[64, 34, 25, 12, 22, 11, 90]
[34, 64, 25, 12, 22, 11, 90]       
[34, 25, 64, 12, 22, 11, 90]
[34, 25, 12, 64, 22, 11, 90]
[34, 25, 12, 22, 64, 11, 90]
[34, 25, 12, 22, 11, 64, 90]
[25, 34, 12, 22, 11, 64, 90]
[25, 12, 34, 22, 11, 64, 90]
[25, 12, 22, 34, 11, 64, 90]
[25, 12, 22, 11, 34, 64, 90]
[12, 25, 22, 11, 34, 64, 90]
[12, 22, 25, 11, 34, 64, 90]
[12, 22, 11, 25, 34, 64, 90]
[12, 11, 22, 25, 34, 64, 90]
[11, 12, 22, 25, 34, 64, 90]

But if i convert it to a list using any of these ways: (One at a time)

#1
print(list(generator))

#2
l=[i for i in generator] 
print(l)

#3
l=[]
for i in generator:    
    l.append(i)
print(l)

#4
l=[*generator]
print(l)

It will give me o/p as below : (all states are already sorted or last states)

[[11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90]] 

Now i don’t understand why this happens but , this gets even more confusing for me when i do
any of these and it gives me the desired o/p.

#1
l=[list(i) for i in generator]  #list(i)
print(l)

#2
l=[i.copy() for i in generator]  #i.copy()
print(l)

#3
l=[]
for i in generator: 
    l.append(list(i))  #list(i)
print(l)

#4
l=[]
for i in generator:
    l.append(i.copy()) #i.copy()
print(l)

o/p:

[[64, 34, 25, 12, 22, 11, 90], [34, 64, 25, 12, 22, 11, 90], [34, 25, 64, 12, 22, 11, 90], [34, 25, 12, 64, 22, 11, 90], [34, 25, 12, 22, 64, 11, 90], [34, 25, 12, 22, 11, 64, 90], [25, 34, 12, 22, 11, 64, 90], [25, 12, 34, 22, 11, 64, 90], [25, 12, 22, 34, 11, 64, 90], [25, 12, 22, 11, 34, 64, 90], [12, 25, 22, 11, 34, 64, 90], [12, 22, 25, 11, 34, 64, 90], [12, 22, 11, 25, 34, 64, 90], [12, 11, 22, 25, 34, 64, 90], [11, 12, 22, 25, 34, 64, 90]]

Can anyone explain it to me why the difference in outputs between last 2 inputs ?
What changes when i do i.copy() or list(i) for every state in the generator ?
Thanks for reading.

Asked By: Random

||

Answers:

There is a single arr object that is being mutated inside the generator.

If you print it in between the mutations, you get a snapshot of each individual state, since the current state of arr is printed to the console before it changes.

When you capture that arr object in a list comprehension, though, you just have a list with a bunch of references to that one object, so as arr changes, so too does every entry in your list.

If you wanted to change that, you could yield arr.copy() inside your generator to make sure that you’re always yielding a unique copy that won’t change as the generator continues to iterate. This would have the same effect that you’re seeing when you call copy() or list() outside of the generator (either of those methods results in a new copy which does not change when arr is modified inside the generator).

Answered By: Samwise
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.