通过前几篇的积累,节点类添加了创建、拼接和删除的功能,本篇尝试一下使用这些已定义过的函数方法快速重载链表间的算术运算:
加法
相当于用之前的 push,append,cat 方法重载加法,也是非常恰当的。
加法重载的约定
当两个“加数”都为链表或节点时,后者拼接到前者尾部;当有一个非节点“加数”时,作为追加元素来处理。头插法为左加,追加法为右加,左、右加法分别用 __add__() 和 __radd__() 方法来重载。没有重载前,对Node类型做加法会报错的;如果只重载左加法不重载右加法,python 也只能识别 “node + 1” 为把1追加到node尾部;但不能识别出 “1 + node” 的用意,不支持的类型加法,并报错:TypeError: unsupported operand type(s) for +: 'int' and 'Node' 。
左加法
先增加一个左加法重载,用Node.build快速实现,不别写代码了:
def __add__(self,*data): if self.val is None: return Node.build(*data) return Node.build(self,*data)
完成的代码如下:
class Node(): def __init__(self, value=None, Next=None): self.val = value self.next = Next if type(self.next)==Node and self.next.val==None: self.next=None def __repr__(self): return f'Node({self.val}->{self.next})' def __str__(self): return f'{self.val}->{self.next}' def __len__(self): if self.val is None: return 0 length,ptr = 0,self while ptr is not None: length += 1 ptr = ptr.next return length def __eq__(self, other): ptr1,ptr2 = self,other if len(ptr1)!=len(ptr2): return False while ptr1 is not None: if ptr1.val!=ptr2.val: return False ptr1 = ptr1.next ptr2 = ptr2.next else: return True def size(self): return self.__len__() @property def length(self): return self.size() @property def value(self): return self.val @property def values(self): ret,ptr = [],self while ptr is not None: ret.append(ptr.val) ptr = ptr.next return ret def pprint(self): items = [str(i) for i in self.values] if items==[]: print('None->None') elif len(items)==1: print(f'{items[0]}->None') else: print('->'.join(items)+'->None') def isNode(node): return isinstance(node,Node) and isinstance(node.next,(Node,type(None))) def build(*data, split=True): '''把数据转换成节点链表''' lst,ret = [],Node() for val in data: if type(val) is str: if not split: lst.append(val) continue if str=='': continue try: num = int(val) lst.extend([int(_) for _ in val]) except: lst.extend([_ for _ in val]) elif hasattr(val,'__iter__'): lst.extend([_ for _ in val]) elif type(val) is Node: if val is not None: lst.extend(val.values) else: lst.append(val) ret = Node() for i in lst[::-1]: ret = Node(i, ret) return ret def copy(node): ret = Node() ptr1,ptr2 = node,ret while ptr1 is not None: ptr2.next = Node(ptr1.val) ptr1,ptr2 = ptr1.next,ptr2.next return ret.next def __add__(self,*data): if self.val is None: return Node.build(*data) return Node.build(self,*data)
测试结果:
''' >>> Node(1)+Node(2) Node(1->2->None) >>> Node(1)+Node(2)+3 Node(1->2->3->None) >>> Node(1)+Node(2)+3+4 Node(1->2->3->4->None) >>> 1+Node(2) Traceback (most recent call last): File "<pyshell#8>", line 1, in <module> 1+Node(2) TypeError: unsupported operand type(s) for +: 'int' and 'Node' >>> '''
右加法
当节点在右面做加法时会报错,所以要增加一个右加法,再看效果:
def __radd__(self,*data): return Node.build(*data,self) ''' >>> 1+Node(2) Node(1->2->None) >>> 1+Node(2,Node(3)) Node(1->2->3->None) >>> 1+2+Node(3) Node(3->3->None) >>> (1,2)+Node(3) Node(1->2->3->None) >>> "'
可以成功实现,但要注意:加法是从左到右逐个计算的,如上红色标注的会先计算1+2的;所以使用右加法时,想在左边用头插法追加多个元素时,一定要用括号把后面的先加;或者选用元组或列表等可迭代的对象作追加元素的容器; 或者索性在最左边给个空节点,变回到左加法。如下三种方式都可以:
>>> 1+(2+(3+Node(4))) Node(1->2->3->4->None) >>> [1,2,3]+Node(4) Node(1->2->3->4->None) >>> Node()+1+2+3+Node(4) Node(1->2->3->4->None) >>>
注意:左加法指“被加数”在左边,要加的“数”在右边;右加法反之。
自加法
即重载 __iadd__() 方法,默认的话自动继承自左加法,直接看测试:
>>> (1,2)+Node(3) Node(1->2->3->None) >>> node = (1,2)+Node(3) >>> node += 4 >>> node Node(1->2->3->4->None) >>>
若要把它重载成右加法,则在类里__radd__后加一行“__iadd__ = __radd__”:
def __radd__(self,*data): return Node.build(*data,self) __iadd__ = __radd__ ''' >>> node = (1,2)+Node(3) >>> node += 4 >>> node Node(4->1->2->3->None) >>> '''
减法
相当于前几篇中定义的删除方法,但重载减法之前要检测一下“被减数”中包不包含“减数”。
包含运算 in
直接调用.values属性来“伪实现”这个功能。
def __contains__(self, other): if type(other) is not Node: return other in self.values sub,subs = other.values,self.values if len(sub)>len(subs): return False subs=[subs[i:i+len(sub)] for i in range(len(subs)-len(sub)+1)] return sub in subs
测试结果:
>>> node = Node()+1+2+3+4+5 >>> 1 in node True >>> 2 in node True >>> 5 in node True >>> 0 in node False >>> n1 = Node(2,Node(3)) >>> n2 = Node(2,Node(4)) >>> n1 in node True >>> n2 in node False >>>
左减法
链表间的减法就不像加法一样还要分左右,只有“被减数”减去“减数”,重载 __sub__即可:
def __sub__(self, other): if other not in self: raise BaseException("A-B Error:B not in A") subs = self.values if type(other) is not Node: idx = subs.index(other) subs = subs[:idx]+subs[idx+1:] return Node.build(subs) sub = other.values tmp = [subs[i:i+len(sub)] for i in range(len(subs)-len(sub)+1)] idx = tmp.index(sub) subs = subs[:idx]+subs[idx+len(sub):] return Node.build(subs) ''' >>> a = Node()+range(1,10) >>> a Node(1->2->3->4->5->6->7->8->9->None) >>> a - 2 -3 - 5 - 7 Node(1->4->6->8->9->None) >>> a Node(1->2->3->4->5->6->7->8->9->None) >>> a - Node.build(3,4,5,6) Node(1->2->7->8->9->None) >>> a Node(1->2->3->4->5->6->7->8->9->None) >>> a -= Node.build(3,4,5,6) >>> a Node(1->2->7->8->9->None) >>>
注:直接调用build()函数和values属性伪实现了减法效果。做减法前需要先判断一下“被减数”是否包含“减数”,包含则减去,不包含则会报错。
自减法
自减法__iadd__自动继承__add__不用另行写代码。
乘法
左乘法
乘法约定为数乘,即乘以一个整数n,链表的长度扩大n倍;另外尝试一下非数乘的情况:即当两个链表相乘时,用它们的数据域对应相乘的积做各节点的值。
def __mul__(self, other): if type(other) is Node: n1,n2 = self.values,other.values product = [p[0]*p[1] for p in zip(n1,n2)] return Node.build(product) if other<0 or type(other) is not int: raise TypeError("other is a non-negetive Integer") if other==0:return Node() ret = self.copy() for _ in range(1,other): self += ret return self __rmul__ = __mul__ ''' >>> a = Node() + range(1,3) >>> a * 0 Node(None->None) >>> a * 1 Node(1->2->None) >>> a * 2 Node(1->2->1->2->None) >>> a * 5 Node(1->2->1->2->1->2->1->2->1->2->None) >>> >>> 3 * a Node(1->2->1->2->1->2->None) >>> a Node(1->2->None) >>> a *= 5 >>> a Node(1->2->1->2->1->2->1->2->1->2->None) >>> >>> >>> a = Node() + range(1,8) >>> b = Node(2) * 7 >>> a * b Node(2->4->6->8->10->12->14->None) >>> b * a Node(2->4->6->8->10->12->14->None) >>> '''
右乘法
右乘也要重载,否则右乘时number * Node 会报错,添加一行:__rmul__ = __mul__ 即可。
自乘法
自乘法__imul__自动继承__mul__不用另行写代码。
除法
除法有两种:除和整除,分别是 __truediv__ 、__floordiv__,暂且把两种除法都看作是和整数作运算,并约定:链表除以一个整数n,即长度变为原来1/n。
def __truediv__(self, i): if i<0 or type(i) is not int: raise TypeError("i must be a positive Integer") if i==1: return self if self.next is None: return Node() num = self.values num = num[:len(num)//i] return Node.build(num) __floordiv__ = __truediv__ ''' >>> a = Node()+range(1,3) >>> a *= 5 >>> a / 5 Node(1->2->None) >>> a Node(1->2->1->2->1->2->1->2->1->2->None) >>> a/3 Node(1->2->1->None) >>> a = Node()+range(1,3) >>> a *= 6 >>> a Node(1->2->1->2->1->2->1->2->1->2->1->2->None) >>> a / 2 Node(1->2->1->2->1->2->None) >>> a Node(1->2->1->2->1->2->1->2->1->2->1->2->None) >>> a // 3 Node(1->2->1->2->None) >>> a Node(1->2->1->2->1->2->1->2->1->2->1->2->None) >>> a /= 3 >>> a Node(1->2->1->2->None) >>> a /= 2 >>> a Node(1->2->None) >>> '''
同样 __itruediv__、__ifloordiv__ 自动继承不用另行写代码。
左移和右移
对一个整数来说,有按二进制位进行左移和右移的运算,相当于一个整数乘以或除以2;
对一个链表来说,调用上篇的 rotate(n) 方法来重载左移、右移运算(对应的内置方法 __lshift__ 和 __rshift__ ),模仿出来的效果还是蛮逼真的:
def __lshift__(self, n): return self.rotate(n) def __rshift__(self, n): return self.rotate(-n)
测试效果:
>>> a = Node()+1+2+3+4+5 >>> a Node(1->2->3->4->5->None) >>> a>>1 Node(5->1->2->3->4->None) >>> a Node(1->2->3->4->5->None) >>> a>>3 Node(3->4->5->1->2->None) >>> a Node(1->2->3->4->5->None) >>>
参与运算的链表不发生变化,如要改变值的用 >>= 或 <<= 来赋值:
>>> b = Node()+1+2+3+4+5 >>> b Node(1->2->3->4->5->None) >>> b >>= 2 >>> b Node(4->5->1->2->3->None) >>> b >>= 1 >>> b Node(3->4->5->1->2->None) >>>
或者直接在代码中加入覆盖原链表的代码:
def __lshift__(self, n): ret = self.rotate(n) self.val,self.next = ret.val,ret.next return ret def __rshift__(self, n): ret = self.rotate(-n) self.val,self.next = ret.val,ret.next return ret ''' >>> node = Node.build(1,2,3,4,5) >>> node Node(1->2->3->4->5->None) >>> node >> 1 Node(5->1->2->3->4->None) >>> node >> 2 Node(3->4->5->1->2->None) >>> node >> 3 Node(5->1->2->3->4->None) >>> node Node(5->1->2->3->4->None) >>> node << 6 Node(1->2->3->4->5->None) >>> node << 1 Node(2->3->4->5->1->None) >>> node << 1 Node(3->4->5->1->2->None) >>> node >> 2 Node(1->2->3->4->5->None) >>> node Node(1->2->3->4->5->None) >>> '''
本篇的内容纯粹是为了学习如何实现运算符的重载,除了包含运算in比较有用,其它都没什么实用性,可以试着用正规的遍历方法来实现它的功能。另外还有一些__neg__、 __pos__、 __abs__、__mod__、__divmod__、__pow__、__round__、__invert__、__and__ 、 __or__ 、 __xor__,以及众多的比较运算符,都可以用来重载对应的功能。
——*** 本篇完 ***——