Почему удаляется объект из массива Python

Решаю вот эту кату на codewars. Задание вкратце: посчитать определитель квадратной матрицы произвольного размера. Собственно, мое решение:

def determinant(matrix):
    print('Matrix: ',matrix)
    
    def minor(i,m):
        #здесь специально копирую переданную матрицу, чтобы с ней ничего не случилось
        major=m.copy()
        print('Major: ',major)
        major.pop(0)
        for row in major:
            row.pop(i)
        print('Minor: ',major)
        return major
    
    if len(matrix)==1:
        print('Det: ',matrix[0][0])
        return matrix[0][0]
    
    sum=0
    
    for j in range(len(matrix)):
        print('Iter: ',j)
        print('Before matrix: ',matrix)
        sum+=((-1)**j)*matrix[0][j]*determinant(minor(j,matrix))#вот после этой строки удаляется элемент
        print('After matrix: ',matrix)
        
    print('Det: ',sum)
    return sum

и вот что получаю в логах:

Matrix:  [[1, 3], [2, 5]]
Iter:  0
Before matrix:  [[1, 3], [2, 5]]
Major:  [[1, 3], [2, 5]]
Minor:  [[5]]
Matrix:  [[5]]
Det:  5
After matrix:  [[1, 3], [5]]
Iter:  1
Before matrix:  [[1, 3], [5]]
Major:  [[1, 3], [5]]
Traceback (most recent call last):
...
line 9, in minor
    row.pop(i)
IndexError: pop index out of range

То есть, в итоге, на первой же итерации после строки sum+=((-1)**j)*matrix[0][j]*determinant(minor(j,matrix)) у меня матрица [[1,3],[2,5]] чудесным образом превращается в [[1,3],[5]], при том, что внутри функции minor я специально работаю с копией. Кто-нибудь может объяснить, как это происходит?


Ответы (2 шт):

Автор решения: Hat

Так происходит, потому что на первой же итерации def minor(i,m): возвращает major, а major при этом равен Minor: [[5]] из ваших же логов. Далее вы вызываете determinant и передаёте результат minor, то есть [[5]]. После этого у вас Matrix: [[5]].

Решения можно придумать разные, но если вы хотите решать всё в функции determinant, вы не должны вызывать её рекурсивно. Кроме данной проблемы, у вас также каждый раз будет обнуляться sum.

→ Ссылка
Автор решения: kerstem

Большое спасибо @CrazyElf за подсказку. Действительно, в определении функции minor попытка не изменить передаваемый массив m копированием major=m.copy() дает не интуитивный результат, а именно:

  • изменения первого слоя major типа major.pop() не изменяют передаваемый массив
  • изменения второго слоя major типа major[i].pop() изменяют вложенные массивы передаваемого массива

из за этого после minor(0,[[1,2],[3,4]]) не удаляется нулевая строка, но удаляется нулевой элемент первой строки, и в итоге передаваемый массив превращается в [[1,2],[4]] и далее возникает ошибка.

Тривиальным на мой взгляд является замена копии исходного массива на массив с копиями вложенных массивов: major=[x.copy() for x in m]:

def determinant(matrix):
    
    def minor(i,m):
        major=[x.copy() for x in m]
        major.pop(0)
        
        for row in major:
            row.pop(i)
            
        return major

    if len(matrix)==1:
        return matrix[0][0]
    
    sum=0
    
    for j in range(len(matrix)):
        sum+=((-1)**j)*matrix[0][j]*determinant(minor(j,matrix))
        
    return sum

Однако работая с большими вложенностями, уместнее использовать указанную @CrazyElf библиотеку from copy import deepcopy

→ Ссылка