Удалить общие ссылки в списке-списке?

Хорошо, позвольте мне объяснить проблему на простом примере:

l = [[0]]*3       # makes the array [[0], [0], [0]]
l[0][0] = 42      # l becomes [[42], [42], [42]]
from copy import deepcopy
m = deepcopy(l)   # m becomes [[42], [42], [42]]
m[0][0] = 2       # m becomes [[2], [2], [2]]

Это основная проблема с общими ссылками. За исключением случаев, когда обычно возникает такая проблема, deepcopy - наш друг. В настоящее время я сделал это, чтобы решить мою deepcopy предательства в deepcopy:

l = [[0]]*3       # makes the array [[0], [0], [0]]
import JSON
m = JSON.loads(JSON.dumps(l)) # m becomes [[0], [0], [0]] with no self references

Я ищу менее неэффективный и менее глупый способ обработки собственных ссылок.

Конечно, я бы не создавал подобные массивы специально, но мне нужно разобраться со случаем, когда кто-то дает его моему коду. Выполнение моего "решения" на больших массивах происходит медленно, и у меня есть много уровней вложенных массивов, я не могу позволить себе сделать строку такой большой для этих зверей.

Ответ 1

Здесь подход, который будет работать с любой комбинацией списков, диктов и неизменных значений.

def very_deep_copy(obj):
    if isinstance(obj, list):
        return [very_deep_copy(item) for item in obj]
    elif isinstance(obj, dict):
        return {k: very_deep_copy(v) for k,v in obj.items()}
    else:
        return obj

l = [[0]]*3 
m = very_deep_copy(l)
m[0][0] = 2
print(m)

Результат:

[[2], [0], [0]]

Ответ 2

Я собираюсь оспорить предположение, что правильное решение - копировать общие объекты. Вы говорите, что

Конечно, я бы не создавал подобные массивы специально, но мне нужно разобраться со случаем, когда кто-то дает его моему коду.

но если кто-то передает вам ввод с неожиданным совместным использованием объекта, в его коде есть ошибка. Если ваш код замечает ошибку, ваш код должен сообщить им об этом, выдав исключение, чтобы помочь им исправить свои ошибки.

Большая часть кода просто предполагает, что ввод не имеет нежелательного совместного использования объектов. Если вы все равно хотите его обнаружить, то, вероятно, лучшим вариантом будет ручной обход, тем более что ожидается, что ваш ввод будет JSON-сериализуемым:

def detect_duplicate_references(data):
    _detect_duplicate_references(data, set())

def _detect_duplicate_references(data, memo):
    if isinstance(data, (dict, list)):
        if id(data) in memo:
            raise ValueError(f'multiple references to object {data} detected in input')
        memo.add(id(data))
    if isinstance(data, list):
        for obj in data:
            _detect_duplicate_references(obj, memo)
    if isinstance(data, dict):
        for obj in data.values():
            _detect_duplicate_references(obj, memo)

Ответ 3

l = [[0] for _ in range(3)]
l[0][0] = 2
print(l)

печатает:

[[2], [0], [0]]

Кроме того, кстати, в вашем коде deepcopy() результат, который он сделал, потому что вы передали список, в котором уже есть элементы с одинаковыми ссылками.

from copy import deepcopy
m = deepcopy(l)
m[0][0] = 3
print(l) # prints [[2], [0], [0]]
print(m) # prints [[3], [0], [0]]

вы можете увидеть здесь, что он делает то, что мы ожидаем, это без проблем

Ответ 4

Предполагая, что только тип структуры будет список, это должно работать для списка произвольной глубины и сложности:

def customcopy(l):
    return [customcopy(e) if type(e) is list else e for e in l]

l = [[0]]*3
x = customcopy(l)
x[0][0] = 3

>>> x
[[3], [0], [0]]

Ответ 5

Не уверен, что это эффективно, но вы можете попробовать:

l = [deepcopy(elt) for elt in l]

Ответ 6

Для одноуровневой копии вы можете просто проверить ссылки. Для более глубоких копий просто сделайте это рекурсивно.

from copy import deepcopy

def copy_value(l):
    l = list(l)
    new_list = []
    while l:
        item = l.pop(0)
        if item not in new_list:
            new_list.append(item)
        else:
            new_list.append(deepcopy(item))
    return new_list

l = [[0]]*3 
m = copy_value(l)
m[0][0] = 2
print(m)

печать

[[2], [0], [0]]

Ответ 7

Другой способ с пониманием списка:

def super_deep_copy(l):
   return [i.copy() for i in l]
l = [[0]]*3
l = super_deep_copy(l)
l[0][0] = 2

И сейчас:

print(l)

Является:

[[2], [0], [0]]

Ответ 8

Вы могли бы просто сделать

l = [[list([0])]*3] 

l[0][0]=1
print(l) #[[1, [0], [0]]]