通过前几篇的积累,节点类添加了创建、拼接和删除的功能,本篇尝试一下使用这些已定义过的函数方法快速重载链表间的算术运算:

加法

相当于用之前的 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

 完成的代码如下:

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

'''
>>> 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__,以及众多的比较运算符,都可以用来重载对应的功能。
 

——*** 本篇完 ***——