试图为双向链表编写交换位置函数

时间:2014-07-07 12:42:46

标签: python swap doubly-linked-list

我一直试图在Python中创建一个简单的链表实现作为代码练习,尽管我有大部分工作(插入,删除,漂亮打印,交换两个节点的内容),我和#39;已经被困在交换两个节点几天了。

我在互联网上四处看看,大多数人似乎建议删除/插入节点或交换数据。两者都是非常精细和功能性的选项,但我想挑战自己,看看我是否可以交换节点"纠正"办法。

理想情况下,我希望有一个可以处理所有边缘情况的通用函数(移动到开始,结束和交换随机节点)。事实证明,这比我预期的更具挑战性。

我用笔和纸进行了一些实验并搜索了一下,我找到了以下讨论和示例实现:

我遇到的问题是我的node1.next和我的node2.prev被交换了,我的node2.next引用了自己,而不是列表中的下一个节点。

示例实现页面上的注释专门解决了这个问题,并提到它的实现不应该发生。

我似乎无法弄清楚我做错了什么。我想我可以"欺骗"并强制他们在结尾处采用正确的值,但是当节点是第一个/最后一个时,这会产生很多问题。

__author__ = 'laurens'
from django.core.exceptions import ObjectDoesNotExist
import logging
import copy

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

class DoublyLinkedList(object):
    """This class implements a basic doubly linked list in Django, it depends
    on a Django model with the following field:

    id : PK
    prev: integer previous node
    data_field: Foreign key
    next: integer next node

    the prev and next fields don't need to be self-referencing

    When instantiating the class you have to link this class to a Django model
     and specify a data field. The data field can link to a foreign key
     or contain data
    """

    def __init__(self, doubly_linked_list_model, data_field):
        self.doubly_linked_list_model = doubly_linked_list_model
        self.data_field = data_field

    def get_node_from_node_id(self, node_id=None):
        """This function returns the node associated with a certain node_id"""
        if node_id is None:
            node = None
        else:
            try:
                node = self.doubly_linked_list_model.get(id=node_id)
            except ObjectDoesNotExist:
                node = None

        return node

    @staticmethod
    def _update_node(node, prev=None, next=None):

        node.prev = prev
        node.next = next

        logger.debug('updating node: %s', node.id)
        logger.debug('node.prev = %s', node.prev)
        logger.debug('node.next = %s', node.next)

        try:
            node.save()
        except Exception as e: #Todo: specify this
            logger.debug('Error saving node: %s', node.id)

    def move_node(self, node1=None, node2=None):
        """
        This function swaps the position of node1 with the position of node2
        """

        #swapping two nodes!
        logger.debug('Swapping two random nodes!: %s, %s', node1.id, node2.id)

        # Swapping next nodes
        logger.debug('Swapping next node')
        tmp = copy.deepcopy(node1.next)

        self._update_node(node=node1,
                          prev=node1.prev,
                          next=node2.next)

        #Todo: Check if tmp changes or is stored as a copy
        self._update_node(node=node2,
                          prev=node2.prev,
                          next=tmp)

        if node1.next is not None:
            logger.debug('Connect the next node to node 1')
            node_next = self.get_node_from_node_id(node1.next)
            self._update_node(node=node_next,
                              prev=node1.id,
                              next=node_next.next)

        if node2.next is not None:
            logger.debug('Connect the next node to node 2')
            node_next = self.get_node_from_node_id(node2.next)
            self._update_node(node=node_next,
                              prev=node2.id,
                              next=node_next.next)

        logger.debug('Swap prev nodes')
        tmp = copy.deepcopy(node1.prev)

        self._update_node(node=node1,
                          prev=node2.prev,
                          next=node1.next)

        self._update_node(node=node2,
                          prev=tmp,
                          next=node2.next)

        # Connect the node before node1 to node 1
        if node1.prev is not None:
            logger.debug('Connect the prev to node 1')
            node_prev = self.get_node_from_node_id(node1.prev)
            self._update_node(node=node_prev,
                              prev=node_prev.prev,
                              next=node1.id)

        # Connect the node before node2 to node 2
        if node2.prev is not None:
            logger.debug('Connect the prev to node 2')
            node_prev = self.get_node_from_node_id(node2.prev)
            self._update_node(node=node_prev,
                              prev=node_prev.prev,
                              next=node2.id)

_update_node function只是接受我的输入并将其提交到数据库;它可以处理None个值。

get_node_from_node_id接受一个整数作为输入,并返回与之关联的节点对象。我使用它,以便我不必在数据库中使用自引用外键(这是正确的术语吗?),现在我想继续以这种方式工作。一旦我完成了这项工作,我将继续以正确的方式将其修复到数据库中。

1 个答案:

答案 0 :(得分:1)

提示:当我提供a minimal, complete, verifiable example (MCVE)时,我会更快地得到很多的答案,也称为short, self-contained, compilable example (SSCCE)

您的示例代码无法证明问题,因此我们无法为您提供帮助。 请让我们帮助您。

我运行了你的示例代码,但我没有看到任何输出。

  

我遇到的问题是...我的node2.next引用自身和   不到列表中的下一个节点。

为什么node2.next引用node2有问题?

据我所知,到目前为止你给我们的代码部分工作得很好。

我曾经遇到过的一些最困难的调试会议,只有当我意识到一切都在正常工作时,我认为我正在寻找的“错误”实际上并不存在。

是的,在算法中途有一个中间步骤,节点引用自身,这看起来可能显然是错误的。

然后算法的后半部分进行了进一步的更改。 到算法结束时,

  • “next”链正确地从开头到结束一直运行,
  • “正向”链正确地以与“下一个”链相反的顺序运行,从结尾到开头,
  • 这些链的顺序与原始顺序类似,只是node1和node2交换了逻辑位置(但仍处于相同的物理位置)。
  • 每个节点都指向2个其他节点(或指向NULL节点),从不指向自身。

这不是你想要的吗?

当我运行以下测试脚本时,我得到了很多输出 - 但我没有看到输出有任何问题。 (我使用带有整数索引的简单数组而不是真正的指针或引用,以使代码与SQL数据库相比更短,更容易调试。)

您是否介意指出哪一行输出不符合您的预期,并说明您对该输出线的预期实际说法?

#!/usr/bin/env python
# https://stackoverflow.com/questions/24610889/trying-to-write-a-swap-position-function-for-a-doubly-linked-list
# Is this the shortest possible program that exhibits the bug?
# Before running this probram, you may need to install
#     sudo apt-get install python-django
# 2015-03-12: David added some test scaffolding
# 2014-07-07: Laurens posted to StackOverflow

__author__ = 'laurens'
from django.core.exceptions import ObjectDoesNotExist
import logging
import copy

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

class DoublyLinkedList(object):
    """This class implements a basic doubly linked list in Django, it depends
    on a Django model with the following field:

    id : PK
    prev: integer previous node
    data_field: Foreign key
    next: integer next node

    the prev and next fields don't need to be self-referencing

    When instantiating the class you have to link this class to a Django model
     and specify a data field. The data field can link to a foreign key
     or contain data
    """

    def __init__(self, doubly_linked_list_model, data_field):
        self.doubly_linked_list_model = doubly_linked_list_model
        self.data_field = data_field

    def get_node_from_node_id(self, node_id=None):
        """This function returns the node associated with a certain node_id"""
        if node_id is None:
            node = None
        else:
            try:
                node = self.doubly_linked_list_model.get(id=node_id)
            except ObjectDoesNotExist:
                node = None

        return node

    @staticmethod
    def _update_node(node, prev=None, next=None):

        node.prev = prev
        node.next = next

        logger.debug('updating node: %s', node.id)
        logger.debug('node.prev = %s', node.prev)
        logger.debug('node.next = %s', node.next)

        try:
            node.save()
        except Exception as e: #Todo: specify this
            logger.debug('Error saving node: %s', node.id)

    def move_node(self, node1=None, node2=None):
        """
        This function swaps the position of node1 with the position of node2
        """

        #swapping two nodes!
        logger.debug('Swapping two random nodes!: %s, %s', node1.id, node2.id)

        # Swapping next nodes
        logger.debug('Swapping next node')
        tmp = copy.deepcopy(node1.next)

        self._update_node(node=node1,
                          prev=node1.prev,
                          next=node2.next)

        #Todo: Check if tmp changes or is stored as a copy
        self._update_node(node=node2,
                          prev=node2.prev,
                          next=tmp)

        if node1.next is not None:
            logger.debug('Connect the next node to node 1')
            node_next = self.get_node_from_node_id(node1.next)
            self._update_node(node=node_next,
                              prev=node1.id,
                              next=node_next.next)

        if node2.next is not None:
            logger.debug('Connect the next node to node 2')
            node_next = self.get_node_from_node_id(node2.next)
            self._update_node(node=node_next,
                              prev=node2.id,
                              next=node_next.next)

        logger.debug('Swap prev nodes')
        tmp = copy.deepcopy(node1.prev)

        self._update_node(node=node1,
                          prev=node2.prev,
                          next=node1.next)

        self._update_node(node=node2,
                          prev=tmp,
                          next=node2.next)

        # Connect the node before node1 to node 1
        if node1.prev is not None:
            logger.debug('Connect the prev to node 1')
            node_prev = self.get_node_from_node_id(node1.prev)
            self._update_node(node=node_prev,
                              prev=node_prev.prev,
                              next=node1.id)

        # Connect the node before node2 to node 2
        if node2.prev is not None:
            logger.debug('Connect the prev to node 2')
            node_prev = self.get_node_from_node_id(node2.prev)
            self._update_node(node=node_prev,
                              prev=node_prev.prev,
                              next=node2.id)


global_test_array = []
obfuscation = 0xaa

class trivial_test_node_class(object):
    def __init__(self, prev, id, next, data):
        print "initializing test class."
        # self.stuff = [ ["first", 0, 1], ["second", 0, 1] ]
        self.id = id
        self.prev = prev
        self.next = next
        self.data = data
    def something(self):
        print "something"
    def save(self):
        id = self.id
        global_test_array[id] = self
    def __repr__(self):
        # print self.prev, self.id, self.next, self.data
        the_string = "%s(%r)\n" % (self.__class__, self.__dict__)
        return the_string

class trivial_test_list_model_class(object):
    def __init__(self):
        print "initializing test class."
        #self.stuff = [ ["first", 0, 1], ["second", 0, 1] ]
        self.stuff = [ 0 for i in xrange(0,10) ]
        data = 'a'
        for i in xrange(1,10):
            self.stuff[i] = trivial_test_node_class(i-1,i,i+1,data);
            data += 'r' # talk like a pirate day
        self.stuff[-1].next = 0 # use 0 as NULL id.
        global global_test_array
        global_test_array = self.stuff
    def something(self):
        print "something"
    def get(self,id):
        return self.stuff[id]
    def __repr__(self):
        # for i in xrange(1,10):
        #     print self.stuff[i]
        the_string = "%s(%r)" % (self.__class__, self.__dict__)
        return the_string

if __name__ == '__main__':
    # test code that only gets run when this file is run directly,
    # not when this file is imported from some other python script.
    print "Hello, world."
    trivial_test_model = trivial_test_list_model_class()
    print trivial_test_model
    testll = DoublyLinkedList( trivial_test_model, "data" )
    left_node = trivial_test_model.get(3)
    right_node = trivial_test_model.get(4)
    testll.move_node( left_node, right_node )
    print trivial_test_model


# recommended by http://wiki.python.org/moin/vim
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 :