Python链表类定义

huangapple go评论82阅读模式
英文:

Python Linked List Class Definitions

问题

我在学习Python中的数据结构和算法时遇到了一些有趣的事情,特别是关于链表的实现。在定义链表的节点和链表本身的类定义时,我注意到有些示例在初始化方法中同时定义了“Value”和“Next”属性作为参数,而其他示例只将“Value”属性定义为初始化方法的参数,将“Next”属性作为节点类自身的本地定义变量。

我假设后者更准确和安全,因为链表类应该负责修改节点的“Next”属性,这样可以使代码更加安全,并消除了意外修改的可能性。

请问有人可以为我确认这一点,并告诉我我的假设是否准确?如果不是,有人可以更详细地解释一下,并纠正我的假设吗?

我尝试查找如何正确定义链表和节点类的示例。

以下是我所参考示例的摘录...

class Node:
    def __init__(self, value, next_node=None, prev_node=None):
        self.value = value
        self.next = next_node
        self.prev = prev_node

    def __str__(self):
        return str(self.value)

class LinkedList:
    def __init__(self, values=None):
        self.head = None
        self.tail = None
        if values is not None:
            self.add_multiple_nodes(values)

    def __str__(self):
        return ' -> '.join([str(node) for node in self])

    def __len__(self):
        count = 0
        node = self.head
        while node:
            count += 1
            node = node.next
        return count

    def __iter__(self):
        current = self.head
        while current:
            yield current
            current = current.next

    @property
    def values(self):
        return [node.value for node in self]

    def add_node(self, value):
        if self.head is None:
            self.tail = self.head = Node(value)
        else:
            self.tail.next = Node(value)
            self.tail = self.tail.next
        return self.tail

    def add_multiple_nodes(self, values):
        for value in values:
            self.add_node(value)

    def add_node_as_head(self, value):
        if self.head is None:
            self.tail = self.head = Node(value)
        else:
            self.head = Node(value, self.head)
        return self.head

与以下示例进行比较...

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

    def __repr__(self):
        return self.data

class LinkedList:
    def __init__(self):
        self.head = None

    def __repr__(self):
        node = self.head
        nodes = []
        while node is not None:
            nodes.append(node.data)
            node = node.next
        nodes.append("None")
        return " -> ".join(nodes)
英文:

I ran into something interesting whilst learning about Data Structures and Algorithms in Python - particularly the implementation of Linked Lists. While defining the Class Definitions for the Linked List’s Node and the Linked List itself, I noticed some examples define the Node Class with both the “Value” and the “Next” Properties as Arguments in the Initialization Method, and other examples define said Node Class with only the “Value” Property as an Argument in the Initialization Method - keeping the “Next” Property as a Locally Defined Variable from the perspective of the Node Class itself.

I am hypothesizing that the latter is more accurate and secure since it should be the role and responsibility of the Linked List Class to modify the Node’s “Next” property, making the code more secure and removing the possibility of accidental modification.

Could someone confirm this for me and please tell me if my assumptions are accurate? If they are not, could someone break this down for me a little more and correct my hypothesis?

I tried looking up examples of how to properly define Classes for Linked Lists and Nodes.

The following are excerpts of the examples I was referring to…

class Node:
def __init__(self, value, next_node=None, prev_node=None):
    self.value = value
    self.next = next_node
    self.prev = prev_node

def __str__(self):
    return str(self.value)
class LinkedList:
def __init__(self, values=None):
    self.head = None
    self.tail = None
    if values is not None:
        self.add_multiple_nodes(values)
def __str__(self):
    return ' -> '.join([str(node) for node in self])
def __len__(self):
    count = 0
    node = self.head
    while node:
        count += 1
        node = node.next
    return count
def __iter__(self):
    current = self.head
    while current:
        yield current
        current = current.next
@property
def values(self):
    return [node.value for node in self]
def add_node(self, value):
    if self.head is None:
        self.tail = self.head = Node(value)
    else:
        self.tail.next = Node(value)
        self.tail = self.tail.next
    return self.tail
def add_multiple_nodes(self, values):
    for value in values:
        self.add_node(value)
def add_node_as_head(self, value):
    if self.head is None:
        self.tail = self.head = Node(value)
    else:
        self.head = Node(value, self.head)
    return self.head

Compared to…

class Node:
def __init__(self, data):
    self.data = data
    self.next = None

def __repr__(self):
    return self.data

class LinkedList:
def __init__(self):
    self.head = None

def __repr__(self):
    node = self.head
    nodes = []
    while node is not None:
        nodes.append(node.data)
        node = node.next
    nodes.append("None")
    return " -> ".join(nodes)

答案1

得分: 1

以下是您要翻译的部分:

"你正在询问是否在 Node 构造函数中不传递 next 参数更“准确和安全”,并提出以下辩护:

连接列表类的角色和责任应该是修改节点的“Next”属性

这是一个正确的说法,但 Node 构造函数是否具有此额外参数实际上并没有影响。LinkedList 类需要一种将节点链接在一起的方式,这意味着 Node 类必须公开一种方法来实现这一点,无论是通过方法,还是通过直接设置其 next 属性,或者...通过构造函数。例如,如果 LinkedList 具有在给定索引插入值的方法,那么需要设置两个与节点相关的属性:新节点的 next 属性和新节点的前驱节点(或链表的头引用)的 next 属性。"

"使代码更加安全并消除意外修改的可能性。"

当然,LinkedList 类应该被设计得没有意外的余地。风险更多地出现在将使用 LinkedList 类的代码一侧。"

"您提供的两种实现几乎无法比较,因为第二种甚至没有提供操作链表的方法——它没有创建/添加节点到其列表的代码。显然,这些类的用户应该自己创建 Node 实例,这确实是一个不好的主意。第一个实现了一个双向链表,并且还维护对尾节点的引用。由于它提供了管理链表的方法,因此它优于第二种实现。不足的是,第一个实现具有返回 Node 实例给调用者的方法,调用者可以(意外地?)设置此节点的 next 属性,从而可能破坏链表。"

"为了更好地保护链表免受“意外”的干扰,您可以决定不向实例化 LinkedList 的代码公开任何 Node 引用。您可以通过以下措施来实现这一点:"

  • "明确表示头引用(如果有的话,尾引用也是如此)是一个内部实现细节。在Python中,您可以通过使用下划线(_head)来命名它。"
  • "不要在 LinkedList 类中拥有接受 Node 引用作为参数的方法,也不要返回或生成 Node 引用。"

"这样,LinkedList 的用户将永远不会直接访问节点,而只会与 LinkedList 类通信 。"

"在 Node 构造函数中具有 next 参数实际上有助于使 LinkedList 实现更加简洁。"

"以下是这种“保护性” LinkedList 类的可能实现:"

然后是代码示例,您已经提供了完整的代码。

英文:

You're asking whether it is more "accurate and secure" to have a Node constructor that does not take a next argument, and give the following in defence:

> it should be the role and responsibility of the Linked List Class to modify the Node’s “Next” property

This is a true statement, but whether or not the Node constructor has this extra parameter does not really have an impact on that. The LinkedList class will need a way to link nodes together, which means that the Node class has to expose a way to do that, either via a method, or by directly setting its next attribute, or ... via the constructor. For instance, if the LinkedList has a method to insert a value at a given index, then two Node-referencing attributes will need to be set: the new node's next attribute and the next attribute of the new node's predecessor (or the head reference of the linked list).

> making the code more secure and removing the possibility of accidental modification.

The LinkedList class should of course be designed so that there is no room for accidents. The risk is more at the side of the code that will use the LinkedList class.

The two implementations you have provided can hardly be compared, as the second one doesn't even offer methods to manipulate the linked list -- it doesn't have code that creates/adds nodes to its list. Apparently the user of these classes is supposed to create the Node instances themselves, which really is a bad idea. The first one implements a doubly linked list, and also maintains a reference to the tail node. As it provides methods for managing the linked list, it is superior to the second implementation. On the negative side, the first implementation has methods that return Node instances to the caller, who could then (accidentally?) set the next attribute of this node potentially breaking the linked list.

To better protect the linked list from "accidents", you could decide to not expose any Node reference to the code that instantiates a LinkedList. This you don't achieve by removing the next parameter fro the Node constructor, but by forbidding (or at least discouraging) the user of your LinkedList class to tamper with the nodes that are in the list. You can achieve that by taking the following measures:

  • Make clear that the head reference (and tail reference, if there is one) is an internal implementation detail. In Python you do this by naming it with an underscore (_head).
  • Don't have methods in the LinkedList class that take a Node reference as argument, nor that return or yield a Node reference.

This way the user of LinkedList will never access the nodes directly, but will only communicate values with the LinkedList class.

Having the next parameter in the Node constructor can actually help to make the LinkedList implementation more concise.

Here is a possible implementation of such "protective" LinkedList class:

class LinkedList:
    class Node:
        def __init__(self, value, next=None):
            self.value = value
            self.next = next
    
        def get(self, index):
            if index >= 0:
                for _ in range(index):
                    self = self.next
                    if not self:
                        break
                return self
    
    def __init__(self, *values):
        self._head = None
        for value in reversed(values):
            self.push(value)

    def isempty(self):
        return not self._head
    
    def push(self, value):
        self._head = self.Node(value, self._head)

    def pop(self):
        if not self.isempty():
            value = self._head.value
            self._head = self._head.next
            return value

    def insert(self, index, value):
        if index == 0:
            return self.push(value)
        prev = self._head.get(index - 1)
        if prev:
            prev.next = self.Node(value, prev.next)
        
    def delete(self, index):
        if index == 0:
            return self.pop()
        prev = self._head.get(index - 1)
        if prev and prev.next:
            value = prev.next.value
            prev.next = prev.next.next
            return value
        
    def __iter__(self):
        node = self._head
        while node:
            yield node.value  # don't expose nodes; only values
            node = node.next


lst = LinkedList(10, 20, 30, 40)
print(lst.pop())       # 10
print(*lst)            # 20 30 40
lst.insert(1, 10)
print(*lst)            # 20 10 30 40
lst.insert(4, 50)
print(*lst)            # 20 10 30 40 50
lst.delete(4)
print(*lst)            # 20 10 30 40
lst.delete(0)
print(*lst)            # 10 30 40

huangapple
  • 本文由 发表于 2023年6月15日 19:42:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76482111.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定