我在Python之前的大多数编程都是用C ++或Matlab编写的。我没有CS学位(几乎完成了物理学博士学位),但已经完成了一些课程和大量的实际编程。现在,我正在参加Coursera的算法课程(顺便说一下,斯坦福大学的一位教授)。我决定用Python实现homeworks。但是,有时我发现自己想要语言不那么容易支持的东西。我习惯于在C ++中为事物创建类和对象,只是为了将数据组合在一起(即没有方法时)。然而,在Python中,你可以动态添加字段,我基本上最终想要的是Matlab结构。我认为这可能是一个标志,我没有使用好的风格,并以“Pythonic”的方式做事。
下面是我对union-find数据结构的实现(对于Kruskal的算法)。虽然实现相对较短并且运行良好(没有太多的错误检查),但有几个奇点。例如,我的代码假定最初传入union-find的数据是一个对象列表。但是,如果传入明确的数据片段列表(即整数列表),则代码失败。是否有一些更清晰,更Pythonic的方式来实现它?我试图谷歌这一点,但大多数例子都非常简单,并且更多地涉及过程代码(即在python中执行for循环的“正确”方法)。
class UnionFind:
def __init__(self,data):
self.data = data
for d in self.data:
d.size = 1
d.leader = d
d.next = None
d.last = d
def find(self,element):
return element.leader
def union(self,leader1,leader2):
if leader1.size >= leader2.size:
newleader = leader1
oldleader = leader2
else:
newleader = leader2
oldleader = leader1
newleader.size = leader1.size + leader2.size
d = oldleader
while d != None:
d.leader = newleader
d = d.next
newleader.last.next = oldleader
newleader.last = oldleader.last
del(oldleader.size)
del(oldleader.last)
答案 0 :(得分:2)
一般来说,在Python上做这种事情意味着你试图让你的代码不关心给它的东西,至少不是真正需要的东西。
让我们以你的联合查找算法为例。 union-find算法实际上对传递给它的值唯一做的就是比较它们是否相等。因此,要创建一个通常有用的UnionFind
类,您的代码不应该依赖于它接收的值而不是相等测试。特别是,您不应该依赖于能够为值分配任意属性。
我建议解决这个问题的方法是让UnionFind
使用包装器对象来保存给定值以及使算法工作所需的任何属性。您可以按照另一个答案的建议使用namedtuple
,或者创建一个小的包装类。将元素添加到UnionFind
时,首先将其包装在其中一个对象中,然后使用包装器对象存储属性leader
,size
等。唯一的一次访问被包装的东西是检查它是否等于另一个值。
实际上,至少在这种情况下,应该可以安全地假设您的值是可清除的,这样您就可以将它们用作Python字典中的键来查找与给定值对应的包装器对象。当然,并非Python中的所有对象都必须是可以删除的,但是那些不是相对稀少的对象,并且制作能够处理这些对象的数据结构将会有更多的工作。
答案 1 :(得分:0)
如果你不必要的话,更多的pythonic方法是避免繁琐的物体。
class UnionFind(object):
def __init__(self, members=10, data=None):
"""union-find data structure for Kruskal's algorithm
members are ignored if data is provided
"""
if not data:
self.data = [self.default_data() for i in range(members)]
for d in self.data:
d.size = 1
d.leader = d
d.next = None
d.last = d
else:
self.data = data
def default_data(self):
"""create a starting point for data"""
return Data(**{'last': None, 'leader':None, 'next': None, 'size': 1})
def find(self, element):
return element.leader
def union(self, leader1, leader2):
if leader2.leader is leader1:
return
if leader1.size >= leader2.size:
newleader = leader1
oldleader = leader2
else:
newleader = leader2
oldleader = leader1
newleader.size = leader1.size + leader2.size
d = oldleader
while d is not None:
d.leader = newleader
d = d.next
newleader.last.next = oldleader
newleader.last = oldleader.last
oldleader.size = 0
oldleader.last = None
class Data(object):
def __init__(self, **data_dict):
"""convert a data member dict into an object"""
self.__dict__.update(**data_dict)
答案 2 :(得分:0)
要检查参数是否属于预期类型,请使用内置的isinstance()
函数:
if not isinstance(leader1, UnionFind):
raise ValueError('leader1 must be a UnionFind instance')
此外,将docstrings添加到函数,类和成员函数是一个好习惯。这样的函数或方法的文档字符串应该描述它的作用,要传递给它的参数以及返回的内容以及可以引发的异常。
答案 3 :(得分:0)
一种选择是使用词典来存储您需要的有关数据项的信息,而不是直接存储项目的属性。例如,您可以引用d.size
(其中size[d]
是size
个实例),而不是引用dict
。这要求您的数据项可以清除,但不需要在其上分配属性。
以下是您当前代码的直接翻译,以使用此样式:
class UnionFind:
def __init__(self,data):
self.data = data
self.size = {d:1 for d in data}
self.leader = {d:d for d in data}
self.next = {d:None for d in data}
self.last = {d:d for d in data}
def find(self,element):
return self.leader[element]
def union(self,leader1,leader2):
if self.size[leader1] >= self.size[leader2]:
newleader = leader1
oldleader = leader2
else:
newleader = leader2
oldleader = leader1
self.size[newleader] = self.size[leader1] + self.size[leader2]
d = oldleader
while d != None:
self.leader[d] = newleader
d = self.next[d]
self.next[self.last[newleader]] = oldleader
self.last[newleader] = self.last[oldleader]
最小测试用例:
>>> uf = UnionFind(list(range(100)))
>>> uf.find(10)
10
>>> uf.find(20)
20
>>> uf.union(10,20)
>>> uf.find(10)
10
>>> uf.find(20)
10
除此之外,您还可以考虑稍微改变实现,以减少初始化。这是一个不进行任何初始化的版本(它甚至不需要知道它将要处理的数据集)。它使用路径压缩和逐级联合,而不是始终为集合的所有成员维护最新的leader
值。它应该比你当前的代码渐近地快,特别是如果你做了很多工会:
class UnionFind:
def __init__(self):
self.rank = {}
self.parent = {}
def find(self, element):
if element not in self.parent: # leader elements are not in `parent` dict
return element
leader = self.find(self.parent[element]) # search recursively
self.parent[element] = leader # compress path by saving leader as parent
return leader
def union(self, leader1, leader2):
rank1 = self.rank.get(leader1,1)
rank2 = self.rank.get(leader2,1)
if rank1 > rank2: # union by rank
self.parent[leader2] = leader1
elif rank2 > rank1:
self.parent[leader1] = leader2
else: # ranks are equal
self.parent[leader2] = leader1 # favor leader1 arbitrarily
self.rank[leader1] = rank1+1 # increment rank
答案 4 :(得分:-2)
我猜这里的缩进问题只是将代码输入SO的简单错误。你能否创建一个简单的内置数据类型的子类?例如,您可以通过将数据类型放在括号中来创建列表数据类型的子类:
class UnionFind(list):
'''extends list object'''