Python value and reference

Asked

Viewed 141 times

2

Disregarding the way it was made, this is a square matrix transposition code, the person who did came across the error in assigning the values in the latter for and neither I or he could solve: when the Mt matrix receives the values of the m matrix, through deepcopy(m) should not all references have been deleted? So why the error when assigning the values in Mt[ i ][ j ] = m[ j ][ i ]

We know how to transpose matrices in other ways but we would like to know the reason for the error to learn. Follow the code below:

from copy import deepcopy
n = int(input('Digite o tamanho da matriz: '))

linha = [0] * n 
m = [linha] * 2
mT = deepcopy(m) 

print(f'Matriz criada com sucesso: {m}')

for l in range(n):  
    linha = list()
    for c in range(n):
        numero = int( input('Digite um valor para a posição {},{}: '.format(l, c)) )
        linha.append(numero)
    m[l] = linha

for i in range(len(m)):
    for j in range(len(m)):        
       mT[i][j] = m[j][i]

print(f'\nMatriz original: {m}')
print(f'\nMatriz transposta: {mT}')

2 answers

4

Let’s go in parts. First, let’s see what happens when you multiply a list by a number:

lista = [0, 0]
m = [lista] * 2
# m é uma lista contendo 2 listas
print(m) # [[0, 0], [0, 0]]

m[0][0] = 10 # mudar um elemento da primeira lista
print(m) # [[10, 0], [10, 0]] (mudei nas 2 listas)

print(id(m[0]), id(m[1])) # imprimem o mesmo número

The variable m is a list of lists: it has 2 elements, and each of them is a list. Only when doing [lista] * 2, both lists of m point to the same list. Therefore a change in the first list also reflects in the second, since both point to the same list. So much so that the id, the result will be the same number for both (according to documentation, in the specific case of Cpython, id returns the address of the object in memory, so in the above code you can see that both point to the same list).

But and deepcopy, shouldn’t I fix it? No:

lista = [0, 0]
m = [lista] * 2
print(m) # [[0, 0], [0, 0]]
print(id(m[0]), id(m[1])) # imprimem o mesmo número

from copy import deepcopy

m2 = deepcopy(m)
print(m2) # [[0, 0], [0, 0]]
print(id(m2[0]), id(m2[1])) # imprimem o mesmo número (não é o mesmo de m)

# mudar um elemento da primeira lista também afeta a segunda
m2[0][0] = 10
print(m2) # [[10, 0], [10, 0]]

# mas m permanece inalterado
print(m) # [[0, 0], [0, 0]]

deepcopy created another list (so much so that the id of those lists in m2 is different from m), but m2 still has the same characteristic: the two lists he has are references to the same list.

This is because, according to the documentation, deepcopy maintains an internal "memory" dictionary that stores objects that have already been copied. So if it finds the same object again, it uses this internal copy instead of copying again.

That is, first he copies the list that is in m[0] and store in this internal "memory". When it will copy m[1], you will see that it is the same list (because we have seen that both point to the same place), so instead of copying again, it uses the copy that is in the internal "memory". The result is that m2 will have 2 references to the same list.


How to solve?

The simplest is to simply not create the list using *. In fact, since lists in Python are dynamic and can change size as needed, you don’t even need to create them with a certain size. Why boot everything with zero if you will then read the values and overwrite everything? Simply add the elements as they are read:

n = int(input('Digite o tamanho da matriz: '))

m = []
for linha in range(n):
    lista = []
    for coluna in range(n):
        numero = int(input(f'Digite um valor para a posição {linha},{coluna}: '))
        lista.append(numero)
    m.append(lista)

print(f'Matriz criada com sucesso: {m}')

And to create the transposed matrix, just iterate through all the lines simultaneously and create a list at each iteration:

mT = []
for elementos in zip(*m):
    mT.append(list(elementos))

print(mT)

zip iterates through multiple lists at once, and the syntax *m serves to make the unpacking of the list m. That is to say, zip(*m) is the same as doing zip(m[0], m[1], etc..), but to do so I would have to know the size of m. Using the unpacking, i don’t need to know the quantity, all are passed as arguments to zip.

So in practice I’m passing all the lines of the matrix to zip. Thus, in the first iteration, elementos will be a tuple containing the first element of each line. In the second iteration, it will be a tuple with the second element of each, and so on.

With this, just create a list from this tuple and insert in mT. At the end, we have the transposed matrix.

If you want, you can also use a comprehensilist on, much more succinct and pythonic:

mT = [ list(elementos) for elementos in zip(*m) ]

Anyway, you don’t need deepcopy because it is not necessary to create m with a predetermined size. But if you want much to do this, the solution is not to use * to create it:

n = # ler o valor de n

# assim cada sublista é independente (não ocorre o problema de ambas apontarem para a mesma)
m = [ [0] * n for i in range(n) ]

for linha in range(n):
    # Não precisa criar uma outra lista interna
    for coluna in range(n):
        m[linha][coluna] = int(input(f'Digite um valor para a posição {linha},{coluna}: '))

from copy import deepcopy

mT = deepcopy(m)
for i in range(n):
    for j in range(n):
        mT[j][i] = m[i][j]

print(mT)

Notice that I eat now m has already been created with the right size, also do not need to create another list within the for, because I can assign the value directly at the specific position.

But I still prefer to create the lists and add the "on-demand" elements, rather than create the matrix with zeros, fill it, create a copy, etc...

  • 1

    Perfectly explained!! Thank you very much for the lesson!!!

  • @Almirlopesmartins If the answer solved your problem, you can accept it, see here how and why to do it. It is not mandatory, but it is a good practice of the site, to indicate to future visitors that it solved the problem

2

The copy was created when the object m era [linha] * 2, in the case of a repeated list. Mt is a copy but still has a repeated list twice, it just does not share the reference with m.

If you change the creation of Mt to another point can fix this.

from copy import deepcopy
n = int(input('Digite o tamanho da matriz: '))

linha = [0] * n 
m = [linha] * 2

print(f'Matriz criada com sucesso: {m}')

for l in range(n):  
    linha = list()
    for c in range(n):
        numero = int( input('Digite um valor para a posição {},{}: '.format(l, c)) )
        linha.append(numero)
    m[l] = linha

mT = deepcopy(m) 

for i in range(len(m)):
    for j in range(len(m)):        
       mT[i][j] = m[j][i]

print(f'\nMatriz original: {m}')
print(f'\nMatriz transposta: {mT}')

Browser other questions tagged

You are not signed in. Login or sign up in order to post.