Как я могу сделать копию кортежа, содержащего вложенные кортежи в Python?

Я работаю над вопросом, который требует от меня определения функции copy_tree, которая принимает аргумент tree (кортеж, который может содержать кортежи) и возвращает копию дерева (т.е. хранится в другом месте памяти).

Мой текущий код:

def copy_tree(tree):
    new_tree = ()
    for i in tree:
        new_i = (i,) + ()
        new_tree = new_tree + new_i
    return new_tree

Однако это не работает для вложенных кортежей, поскольку кортежи внутри не «копируются», а скорее упоминаются.

например если я убегу

a = ((3, 2), 1, (4,), 5)
b = copy_tree(a)
print(a[0] is b[0])

выход должен быть False.

Как мне сделать копии кортежей?

Обновлено: я не допускается, чтобы использовать модуль deepcopy.

Вы знаете о модуле copy?

MisterMiyagi 18.09.2018 10:06

ваш код не запускается. TypeError: unsupported operand type(s) for +: 'int' and 'tuple'

rocksportrocker 18.09.2018 10:07

Вы можете использовать copy.deepcopy или tuple(map(tuple, tree)), если все кортежи, но почему вы вообще хотите копировать кортежи? Кортежи неизменяемы.

tobias_k 18.09.2018 10:08

нужно сделать a[0]==b[0], думаю, для сравнения значений. Подробнее об этом: stackoverflow.com/questions/132988/…

kada 18.09.2018 10:09

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

roganjosh 18.09.2018 10:10

Фактически, даже copy.deepcopy будет нет создать копию кортежей (is же возвращает True), потому что это не имеет реального смысла.

tobias_k 18.09.2018 10:11

@hadik @roganjosh да, вопрос конкретно требует, чтобы я написал код таким образом, чтобы использование оператора is возвращало False. Нет проблем, если == возвращает True.

Ryan Chng 18.09.2018 10:11

Я в основном согласен с тем, что сказал Тобиас. Одним из преимуществ использования неизменяемых объектов является то, что для них безопасно делиться ссылками. OTOH, кортеж жестяная банка содержит изменяемые объекты, поэтому эта операция глубокого копирования не совсем бесполезна. ;)

PM 2Ring 18.09.2018 10:12

@rocksportrocker, ты прав. Извините, я скопировал неправильный код. Обновил его моим последним кодом, который работает и дает визуально правильный результат, но не проходит тест is.

Ryan Chng 18.09.2018 10:13

@ PM2Ring Верно, но поскольку код OP не создает копии внутренних объектов и не пытается работать рекурсивно, я предположил, что это не так.

tobias_k 18.09.2018 10:14

Кортежи @tobias_k не являются полностью неизменяемыми, если их содержимое неизменяемо. например. ([1], [2]). Если вам нужна глубокая копия этого кортежа, вам нужно будет скопировать и его, и его содержимое.

Dunes 18.09.2018 10:14

@RyanChng он прошел тест на моем компьютере, какую версию Python вы используете?

toti08 18.09.2018 10:15

@Dunes Я знаю об этом, но OP явно пытается скопировать только кортежи, а не их содержимое. См. Мой комментарий выше.

tobias_k 18.09.2018 10:16

@ toti08 он проходит этот тест и на моем, но не проходит несколько частных тестов на веб-сайте автогрейдера, который я использую. Я думаю, что частные случаи включают больше уровней вложенности.

Ryan Chng 18.09.2018 10:19

Поэтому вам следует написать рекурсивную тестовую функцию, которая сравнивает два дерева, применяя тест is на каждом уровне. Таким образом, вы можете гарантировать правильность работы copy_tree.

PM 2Ring 18.09.2018 10:22
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
15
4 515
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вы должны использовать модуль копирования.

Этот модуль имеет две функции, которые соответствуют вашим потребностям: копирование и глубокое копирование. copy делает то же самое, что и copy_tree.

Например, если вы это сделаете:

import copy
l = [[0],[0]]
l2 = copy.copy(l)
l[0] = [1]
l[1][0] = [1]

Тогда l2 будет [[0], [1]] Первый элемент l2 не изменился, потому что вы заменили l первым элементом. Однако второй элемент изменился, потому что вы изменили второй элемент l, который является ссылкой на элемент l2.

Если вы используете deepcopy:

 import copy
 l = [[0],[0]]
 l2 = copy.deepcopy(l)
 l[0] = [1]
 l[1][0] = [1]

Тогда l2 будет [[0], [0]], потому что l и l2 больше не имеют ничего общего.

Теперь заставьте его работать с кортежами в соответствии с вопросом. ;)

PM 2Ring 18.09.2018 10:14

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

def copy_tuple(t):
    output = []
    for i in t:
        if isinstance(i, tuple):
            output.append(copy_tuple(i))
        else:
            output.append(i)
    return tuple(output)

так что:

a = ((3, 2), 1, (4,), 5)
b = copy_tuple(a)
print(a)
print(b)
print(a[0] is b[0])

выведет:

((3, 2), (4,), 5)
((3, 2), (4,), 5)
False

Преобразование в list - это хитрый "прием", чтобы tuple не возвращал только ввод, но вы можете добавить кортеж Почему, который в первую очередь ведет себя так.

tobias_k 18.09.2018 10:22

Вот рекурсивное решение, которое глубоко копирует (вложенные) кортежи, оставляет другие объекты неизменными и не использует модуль copy:

def copy_tree(tree):
    if isinstance(tree, tuple):
        return tuple(map(copy_tree, tree))
        # or, maybe more readable
        # return tuple(copy_tree(x) for x in tree)
    return tree

Рекурсия - определенно самый элегантный подход, если вы не знаете заранее уровни вложенности.

Использование функции map для рекурсивного вызова очень разумно. Проголосовали.

blhsing 18.09.2018 10:25
Ответ принят как подходящий

В вашем коде отсутствует рекурсивный шаг - вы копируете только кортеж верхнего уровня.

def copy_tree(tree):
  new_tree = ()
  for i in tree:
    # i is not copied
    new_i = (i,) + ()
    new_tree = new_tree + new_i
  return new_tree

Добавление рекурсии создает копии слоя каждый дерева. Обратите внимание, что вы можете копировать кортежи Только:

def copy_tree(tree):
  new_tree = ()
  for i in tree:
    # recursively copy i *if it is a tuple*
    if isinstance(i, tuple):
      new_i = (copy_tree(i),)
    else:
      new_i = (i,)
    new_tree = new_tree + new_i
  return new_tree

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

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

def copy_tree(tree):
    children = []
    for child in tree:
        # we *always* preserve a child, so ternary if expresses this better
        child = copy_tree(child) if isinstance(child, tuple) else child
        children.append(child)
    # create a new tuple including all children
    return tuple(children)

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

def copy_tree(tree):
    # generator expression - only run when consumed by tuple later on
    children = (
        copy_tree(child) if isinstance(child, tuple) else child
        for child in tree
    )
    return tuple(children)

Конечно, вы также можете напрямую поместить выражение генератора в tuple.

Это действительно сработало, спасибо! Я еще не изучил isinstance, но я заменил эту строку на if type(i) == tuple. Я все еще учусь на вводном уровне, поэтому я не думаю, что они ищут следующие решения, которые вы опубликовали, но первое помогает мне хорошо понять. Огромное спасибо!

Ryan Chng 18.09.2018 10:32

@RyanChng isinstance обычно предпочтительнее type для подобных вещей. Здесь это не имеет значения, но isinstance более гибкий, поскольку он может тестировать несколько типов за один раз, а isinstance(obj, cls) вернет True, если obj является экземпляром cls или любого дочернего класса cls.

PM 2Ring 18.09.2018 10:51

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

def copy_tree(tree):
    new_tree = ()
    for node in tree:
        if type(node) == tuple:
            new_tree += (copy_tree(node),)
        else:
            new_tree += (node,)
    return new_tree


a = ((3, 2), 1, (4,), 5)
b = copy_tree(a)

print(a[0] is b[0])

и последний отпечаток даст вам False.

Вот простая версия, которая копирует дерево, передавая конструктору tuple рекурсивный генератор. Чтобы проверить это, я написал еще одну рекурсивную функцию compare_trees, которую вы можете использовать для проверки того, что соответствующие кортежи не идентичны (с использованием is) и что соответствующие внутренние элементы имеют одинаковое значение (с помощью ==).

def copy_tree(tree):
    return tuple(copy_tree(u) if isinstance(u, tuple) else u for u in tree)

def compare_trees(tree1, tree2, indent=''):
    print(indent, 'tree1', tree1, 'tree2', tree2, 'identical', tree1 is tree2)
    indent += '  '
    for u1, u2 in zip(tree1, tree2):
        if isinstance(u1, tuple) and isinstance(u2, tuple):
            compare_trees(u1, u2, indent)
        else:
            print(indent, 'item1', u1, 'item2', u2, 'equal', u1 == u2)

a = ((3, 2), 1, (4,), 5, (6, (7, 8)))
b = copy_tree(a)
print(b)
compare_trees(a, b)

выход

((3, 2), 1, (4,), 5, (6, (7, 8)))
 tree1 ((3, 2), 1, (4,), 5, (6, (7, 8))) tree2 ((3, 2), 1, (4,), 5, (6, (7, 8))) identical False
   tree1 (3, 2) tree2 (3, 2) identical False
     item1 3 item2 3 equal True
     item1 2 item2 2 equal True
   item1 1 item2 1 equal True
   tree1 (4,) tree2 (4,) identical False
     item1 4 item2 4 equal True
   item1 5 item2 5 equal True
   tree1 (6, (7, 8)) tree2 (6, (7, 8)) identical False
     item1 6 item2 6 equal True
     tree1 (7, 8) tree2 (7, 8) identical False
       item1 7 item2 7 equal True
       item1 8 item2 8 equal True

Я полагаю, это немного иронично, что тестовый код больше и сложнее, чем код, который мы хотим протестировать, но иногда это неизбежно. ;)

Другие вопросы по теме