英文:
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 aNode
reference as argument, nor that return or yield aNode
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
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论