通過前幾篇的積累,節點類添加了創建、拼接和洗掉的功能,本篇嘗試一下使用這些已定義過的函式方法快速多載鏈表間的算術運算:
加法
相當于用之前的 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__,以及眾多的比較運算子,都可以用來多載對應的功能,
——*** 本篇完 ***——
相關文章鏈接:
《觸“類”旁通1|以單鏈表為例,一步步深入了解類》
《觸“類”旁通2|資料結構入門之單鏈表的創建和遍歷》
《觸“類”旁通3|單鏈表基本操作之找查、洗掉和反轉》
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/293460.html
標籤:python
