Python 基于数组、链表实现的包接口

包接口

包接口的以下操作很有用:知道一个包是否为空,用一次操作就清空一个包,判断一个给定的项是否在包中,以及查看包中的每一项而不用清空包等等。

包操作及其方法
用户的包操作 Bag类中的方法
b = () __init__(self,sourceCollection = None)
b.isEmpty() isEmpty(self)
len(b) __len__(self)
str(b) __str__(self)
item in b __contains__(self,item); 如果包含了__iter__,就不需要该方法
b1 + b2 __add__(self,other)
b == anyObject __eq__(self,other)
b.clear() clear(self)
b.add(item) add(self,item)
b.remove(item) remove(self,item)


开发一个基于数组的实现

由于这是一个基于数组的实现,所以ArrayBag类型的每一个对象,都包含了该包中的一个数组。这个数组可以是已经实现的Array类的一个实例,也可以是另一个基于数组的集合(例如Python的list类型)。

Array类
"""
File: arrays.py

An Array is a restricted list whose clients can use
only [], len, iter, and str.

To instantiate, use

 = array(, )

The fill value is None by default.
"""

class Array(object):
    """Represents an array."""

    def __init__(self, capacity, fillValue = None):
        """Capacity is the static size of the array.
        fillValue is placed at each position."""
        self._items = list()
        for count in range(capacity):
            self._items.append(fillValue)

    def __len__(self):
        """-> The capacity of the array."""
        return len(self._items)

    def __str__(self):
        """-> The string representation of the array."""
        return str(self._items)

    def __iter__(self):
        """Supports iteration over a view of an array."""
        return iter(self._items)

    def __getitem__(self, index):
        """Subscript operator for access at index."""
        return self._items[index]

    def __setitem__(self, index, newItem):
        """Subscript operator for replacement at index."""
        self._items[index] = newItem

ArrayBag类

__init__方法负责设置集合的初始状态。该方法以一个初始的、默认的容量(DEFAULT_CAPACITY)创建了一个数组,并且将这个数组赋给一个名为self._items的实例变量。由于包的逻辑大小可能和数组的容量有所不同,每个ArrayBag对象必须在一个单独的实例变量中记录其逻辑大小。因此,__init__方法将这个名为self._size的变量初始设置为0。

在初始化了两个实例变量之后,__init__方法必须处理一种可能发生的情况,即调用者提供了源集合参数,我们必须把源集合的所有数据添加到ArrayBag对象中。

from arrays import Array

class ArrayBag(object):
    """An array-base bag implementation."""
    # Class variable
    DEFAULT_CAPACITY = 30 #注意:这里设置10的话,之后testbench的b+b会导致超出范围

    # Constructor
    def __init__(self,sourceCollection = None):
        """Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
        self._items = Array(ArrayBag.DEFAULT_CAPACITY)
        self._size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

这个接口中最简单的方法是isEmpty、__len__和clear。如果暂时忽略数组变满的问题,add方法也很简单。

    # Accessor methods 访问方法
    def isEmpty(self):
        """Return True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    def __len__(self):
        """Returns the number of items in self."""
        return self._size

    # Mutator methods 改变对象属性方法
    def clear(self):
        """Makes self become empty."""
        self._size = 0
        self._items = Array(ArrayBag.DEFAULT_CAPACITY)
    def add(self,item):
        """Adds item to self."""
        # check array memory here and increase it if necessary
        self._items[len(self)] = item
        self._size += 1

迭代器

当Python出现for循环被用于一个可迭代的对象的时候,它会运行对象的__iter__方法。根据前面的Array类的__iter__方法,你会发现它只是在底层的列表对象上调用iter函数,并且返回结果。然而,这可能会导致一个很严重的错误,数组可能并不是填满的,但是其迭代器会访问所有的位置,包括那些包含了垃圾值的位置。

为了解决这个问题,新的__iter__方法维护了一个游标,当游标达到了包的长度的时候,__iter__方法的while循环终止。

    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = 0
        while cursor < len(self):
            yield self._items[cursor]
            cursor += 1

以下是使用迭代器的方法

__add__方法将两个集合连接起来,__str__方法使用map和join操作来构建字符串,__eq__方法判断两个对象是否相同。这里的每一个方法,都依赖于包对象是可迭代的这一事实。

    def __str__(self):
        """Returns the string representation of self."""
        return "{" + ", ".join(map(str,self)) + "}"

    def __add__(self,other):
        """Returns a new bag containing the contents of self and other."""
        result = ArrayBag(self)
        for item in other:
            result.add(item)
        return result

    def __eq__(self,other):
        """Returns True if self equals other, or False otherwise."""
        if self is other:
            return True
        if type(self) != type(other) or len(self) != len(other):
            return False
        for item in self:
            if not item in other:
                return False
        return True

in运算符和__contains__方法

当Python识别到集合中使用in运算符的时候,它会运行集合类中的__contains__方法。然而,如果这个类的编写者没有包含这个方法,Python会自动生成一个默认的方法,这个方法在self上使用for循环,针对目标项执行一次简单的顺序搜索,由于对包的搜索的平均性能可能不比线性好,所以我们这里就用__contains__的默认实现。

remove方法

首先检查先验条件,如果不满足就返回一个异常。然后,搜索底层数组以查找目标,最后,将数组中的项向左移动,以填补删除的那项留下的空间,将包的大小减小1。如果有必要的话,还要调整数组的大小(节约空间)。

    def remove(self,item):
        """Precondition: item is in self.
        Raises: KeyError if item is not in self.
        Postcondition: item is removed from self."""
        # Check precondition and raise if necessary
        if not item in self:
            raise KeyError(str(item) + " not in the bag")
        # Search for index of target item
        targetIndex = 0
        for targetItem in self:
            if targetItem == item:
                break
            targetIndex += 1
        # Shift items to the left of target up by one position
        for i in range(targetIndex,len(self) - 1):
            self._items[i] = self._items[i+1]
        # Decrement logical size
        self._size -= 1
        # Check array memory here and decrease it if necessary


开发一个基于链表的实现

看一下ArrayBag类,其中isEmpty、__len__、__add__、__eq__和__str__方法都没有直接访问数组变量,这样在基于链表的实现的时候也不需要做出任何修改,如果一个方法没有访问数组变量,它也就不必访问链表结构变量。所以我们应该尝试将数据结构隐藏在所要实现的对象的方法调用中。

Node类
"""
File: node.py
"""

class Node(object):
    """Represents a singly linked node."""

    def __init__(self, data, next = None):
        self.data = data
        self.next = next
LinkedBag类

初始化数据结构

这里的两个变量是链表结构和逻辑大小,而不是数组和逻辑大小,为了保持一致性,可以使用和前面相同的变量,然而,self._items现在是外部指针而不是数组。默认容量的类变量被忽略了。

from node import Node

class LinkedBag(object):
    """An link-based bag implementation."""

    # Constructor
    def __init__(self,sourceCollection = None):
        """Sets the initial state of self, which includes the contents of sourceCollection, if it's present."""
        self._items = None
        self._size = 0
        if sourceCollection:
            for item in sourceCollection:
                self.add(item)

迭代器

LinkedBag类和ArrayBag类的__iter__方法都使用了基于游标的循环来生成项,不一样的是,游标现在是指向链表结构中节点的一个指针,游标最初设置为外部指针self._items,并且当其变为None的时候停止循环。

    def __iter__(self):
        """Supports iteration over a view of self."""
        cursor = self._items
        while not cursor is None:
            yield cursor.data
            cursor = cursor.next

clear和add方法

ArrayBag中的add方法利用了以常数时间访问数组的逻辑末尾项的优点,如果需要的话,在方法执行之后它会调整数组的大小。LinkedBag中的add方法也是通过将新的项放置在链表结构的头部,从而利用了常数访问时间的优点。

    # Mutator methods
    def clear(self):
        """Makes self become empty."""
        self._size = 0
        self._items = None
    def add(self,item):
        """Adds item to self."""
        self._items = Node(item,self._items)
        self._size += 1

remove方法

类似于ArrayBag中的remove方法,LinkedBag的remove方法必须首先处理先验条件,然后针对目标项进行顺序搜索,当找到了包含目标项的节点的时候,需要考虑以下两种情况:

1.它是位于链表结构的头部的节点,在这种情况下,必须将self._items变量重置为这个节点的next链接。

2.这是第一个节点之后的某个节点。在这种情况下,其之前的节点的next链接必须重置为目标项的节点的next链接。

    def remove(self,item):
        """Precondition: item is in self.
        Raises: KeyError if item is not in self.
        Postcondition: item is removed from self."""
        # Check precondition and raise if necessary
        if not item in self:
            raise KeyError(str(item) + " not in the bag")
        # Search for index of target item
        # probe will point to the target node, and trailer will point to the one before it, if it exists
        probe = self._items
        trailer = None
        for targetItem in self:
            if targetItem == item:
                break
            trailer = probe
            probe = probe.next
        # unlock the node to be detected, either the first one or the one thereafter
        if probe == self._items:
            self._items = self._items.next
        else:
            trailer.next = probe.next
        # decrement logical size
        self._size -= 1

无需更改的几种方法

    # Accessor methods
    def isEmpty(self):
        """Return True if len(self) == 0, or False otherwise."""
        return len(self) == 0
    def __len__(self):
        """Returns the number of items in self."""
        return self._size

    def __str__(self):
        """Returns the string representation of self."""
        return "{" + ", ".join(map(str,self)) + "}"

    def __add__(self,other):
        """Returns a new bag containing the contents of self and other."""
        result = LinkedBag(self)
        for item in other:
            result.add(item)
        return result

    def __eq__(self,other):
        """Returns True if self equals other, or False otherwise."""
        if self is other:
            return True
        if type(self) != type(other) or len(self) != len(other):
            return False
        for item in self:
            if not item in other:
                return False
        return True

测试两个包的实现

from ArrayBag import ArrayBag
from LinkedBag import LinkedBag

def test(bagType):
    """Expects a bag type as an argument and runs some tests on objects of that type."""
    lyst = [2013,61,1973]
    print("The list of items added is:",lyst)
    b1 = bagType(lyst)
    print("Expect 3:",len(b1))
    print("Expect the bag's string:",b1)
    print("Expect True:",2013 in b1)
    print("Expect False:",2012 in b1)
    print("Expect the items on seperate lines:")
    for item in b1:
        print(item)
    b1.clear()
    print("Expect {}:",b1)
    b1.add(25)
    b1.remove(25)   
    print("Expect {}:",b1)
    b1 = bagType(lyst)
    b2 = bagType(b1)
    print("Expect True:",b1 == b2)
    print("Expect False:",b1 is b2)
    print("Expect two of each item:",b1 + b2)
    for item in lyst:
        b1.remove(item)
    print("Expect {}:",b1)
    print("Expect crash with KeyError:")
    b2.remove(99)

if __name__ == '__main__':
    #test(ArrayBag)
    test(LinkedBag)

注意:在测试程序中,你可以对任何的包类型运行相同的方法(那些位于包接口中的方法)。这是接口的要旨所在:尽管实现可以变化,但是它保持不变。


两个包运行时性能

这两个包的实现上的操作的运行时间是十分相似的。

in和remove两个操作在实现上都需要线性时间,因为它们都加入了一个顺序搜索。ArrayBag上的remove操作,还必须完成在数组中移动数据项的额外工作,但是总的效果不会比线性阶还差。+、str和iter操作是线性的,==操作的运行时间有几种不同的情况,剩下的操作都是常数时间的(其中ArrayBag的add偶尔因为调整数组大小而达到了线性时间水平)。

两个实现都有预期的内存权衡

当ArrayBag中的数组要好于填满一半的情况的时候,它使用的内存比相同逻辑大小的LinkedBag所使用的内存要小。相比于LinkedBag上对应的操作,ArrayBag的添加操作通常要更快一点,但是删除操作则会慢一些。



本篇文章来自于:数据结构(Python语言描述)这本书的第5章.接口、实现和多态。

你可能感兴趣的:(数据结构——Python实现)