从另一个对象的变量设置对象的变量时奇怪的Python行为

时间:2018-08-16 14:20:47

标签: python python-3.x linked-list pass-by-reference variable-assignment

我创建了一个将反向链接列表的函数,但是发生了一些奇怪的事情,而且我一直无法弄清楚。

我正在尝试在适当位置编辑列表以节省空间,因此该方法更改了原始列表对象,并且不返回任何内容。这意味着如果reverse_list方法是最后一行(为清楚起见,此处将变量重命名):

original_first_node.val = new_first_node.val
original_first_node.next = new_first_node.next

但是由于某些原因,original_first_node.next上的节点链看起来与new_first_node.next的节点链不同,并且现在也是周期性的。

以下是一些可运行的代码,单元测试失败(请参见reverse_list函数中的注释):

import unittest


class Node(object):
    def __init__(self, x):
        self.val = x
        self.next = None


def create_list(list):
    if not list:
        return None
    sentinel = Node(None)
    current = sentinel
    for item in list:
        current.next = Node(item)
        current = current.next
    return sentinel.next


def convert_list(head):
    ret = []
    if head:
        current = head
        while current:
            ret.append(current.val)
            current = current.next
    return ret


def is_list_cyclic(head):
    if not head:
        return False
    tortoise = hare = head
    while hare.next and hare.next.next:
        tortoise = tortoise.next
        hare = hare.next.next
        if tortoise == hare:
            return True
    return False


def reverse_list(head):
    if not head or not head.next:
        return

    current = head
    prev = None
    while current:
        static_next = current.next
        current.next = prev
        prev = current
        current = static_next

    # At this point, prev.next looks great

    head.val = prev.val
    head.next = prev.next

    # head.next is cyclical now for some reason ??


class TestSuite(unittest.TestCase):

    def test_reverse_list(self):
        head = create_list([1, 2, 3, 4])

        reverse_list(head)

        self.assertFalse(is_list_cyclic(head))
        self.assertEqual([4, 3, 2, 1], convert_list(head))


if __name__ == "__main__":
    unittest.main()

1 个答案:

答案 0 :(得分:1)

此Stackoverflow帖子包含有关在Python中传递参数的详细信息:How do I pass a variable by reference?

reverse_list函数中的以下两行是问题所在:

head.val = prev.val
head.next = prev.next

这就是我想发生的事情:

# Marker 1
head.val = prev.val
head.next = prev.next
# Marker 2

Marker 1,列表如下:

None  <---  1  <---  2  <---  3  <---  4

            ^                          ^
            |                          |
          head                       prev

Marker 2,列表如下:

        ----------------------
       |                      |
       |                      |
       |                      v
       ---  4  <---  2  <---  3  <---  4

            ^                          ^
            |                          |
          head                       prev

因此,在reverse_list的末尾,head仍指向第一个节点,但是它的值为4。并且head.next指向包含3的节点,因此您将获得如图所示的循环。

我的建议是您返回对反向列表的第一个节点的引用。修改后的reversed_list如下所示:

def reverse_list(head):
    if not head or not head.next:
        return

    current = head
    prev = None
    while current:
        static_next = current.next
        current.next = prev
        prev = current
        current = static_next

    return prev

您的测试可以修改为:

class TestSuite(unittest.TestCase):

    def test_reverse_list(self):
        head = create_list([1, 2, 3, 4])

        rev = reverse_list(head)

        self.assertFalse(is_list_cyclic(rev))
        self.assertEqual([4, 3, 2, 1], convert_list(rev))

修改

@mattalxndr,在阅读您的评论时,主要的问题似乎是如何在不返回值的情况下反转“就地”列表。我能想到的最简单的解决方案是:

  • 制作列表的副本(保存到copied_list中)
  • 反向copied_list
  • 开始从左到右遍历原始列表
  • 开始从右向左遍历copied_list
  • val中的copied_list复制到原始列表

此技术将创建列表的另一个副本,因此使用O(n)空间。可能存在更好的算法,但是目前我还没有想到。