【数据结构Python描述】使用列表手动实现一个映射类

一、字典与映射

Python的dict类代表了一种高效的数据结构,这种数据结构建立了唯一的键和其对应值之间的映射(Mapping),这种数据结构在需要对键值对进行存储和查找的场景非常高效。如下面代码显示了如何使用字典统计一篇文章中每个单词出现的次数:

freq = {}
for piece in open(filename).read().lower().split():  # 根据空格分割文章
	word = ''.join(c for c in piece if c.isalpha())  # 仅保留字符
	if word:
		freq[word] = 1 + freq.get(word, 0)

max_word = ''
max_count = 0
for (word, count) in freq.items():
	if count > max_count:
	max_word = word
	max_count = c
print('出现次数最多的单词是:', max_word)
print('该单词的出现次数是:', max_count)

实际上,Java语言中的HashMapTreeMap两个类和Python中的dict类功能比较类似,其中HashMap底层使用哈希(也叫散列)技术实现,TreeMap底层使用树实现。

本文将使用列表实现一个与dict功能类似的类。需要注意的是,在名称上为了和Python内建的字典区分开来,本文将手动实现的类似功能的类称为映射

二、基于列表实现映射

1. 映射ADT

为了便于本文讨论,下面首先给出后续实现的映射类所有ADT方法,这些方法和Python的dict类的方法保持一致,下面是映射类最主要的5个ADT方法,即对于映射类的实例对象M

方法名称方法描述
__getitem__()实现该方法后,当M中存在键k时,可使用M[k]语法返回和键k关联的值v,否则抛出KeyError异常。
__setitem__()实现该方法后,当M中存在键k时,可使用M[k] = v语法替换和键k原先关联的值v,否则在M中插入一条键值对记录。
__delitem__()实现该方法后,当M中存在键k时,可使用del M[k]语法删除一条键值对记录,否则抛出KeyError异常。
__len__()实现该方法后,可使用len(M)语法返回映射中记录条目数。
__iter__()实现该方法后,可使用for k in M语法生成映射所有键的一个迭代。

上面5个方法代表了映射对象的最核心功能,即:增、删、改、查。除此之外,下面是映射类其他实用方法,这些方法均可通过使用上述5个方法来实现,即对于映射类的实例对象M

方法名称方法描述
__contains__(k)实现该方法后,可以使用k in M的语法判断映射中是否存在键为k的键值对。
get(k,d=None)如果键k存在映射中则返回M[k],否则返回默认值d,该方法可避免使用M[k]语法时可能抛出的KeyError异常。
setdefault(k,d)如果键k存在,则返回M[k],否则执行M[k] = d且返回d
pop(k,d=None)从映射中删除和键k关联的键值对并返回值v,如果k不存在映射中则返回默认值d(如果dNone则抛出KeyError异常)。
popitem()从映射中删除任意键值对且返回(k, v),如果映射为空则抛出KeyError异常。
clear()清空映射中的所有键值对。
keys()以集合形式返回映射中所有键。
values()以集合形式返回映射中所有值。
items()返回映射中所有(k, v)形式的记录。
update(M2)对映射M2中所有(k, v),使用M[k] = v语法为M赋值。

2. 内建抽象基类MutableMapping

实际上,Python的collections.abc模块中提供了两个官方用于实现字典的类,即MappingMutableMapping,其中:

  • Mapping类包含了上述ADT中所有的非修改类方法,如:__getitem__()keys()items()values()等;
  • MutableMapping类继承了Mapping后新增了所有修改类方法,如:__setitem__(k, v)__delitem__(k)popitem()clear()等。

上述两个类存在的重要意义在于,其为用户自定义映射类提供了框架,具体地,在MutableMapping类中,除__getitem____setitem____delitem____len____iter__这5个是抽象方法(可理解为Java中抽象类中的抽象方法),其余均为基于这5个抽象方法实现的一般性具体方法。

因此,不管后续使用列表、哈希表还是二叉搜索树实现与dict功能相同的映射类时,只需要先继承MutableMapping类,然后在自定义映射类中实现这5个抽象方法即可。

为进一步理解为什么继承MutableMapping自定义映射类只要实现这5个抽象方法即可,可以研究本文附录的MutableMapping源码。例如:

  • 对于支持k in M语法的__contains__(k)方法,其在被自定义映射类继承的MutableMapping中的实现依赖于__getitem__方法的具体实现:
def __contains__(self, key):
    try:
        self[key]  # 调用__getitem__方法
    except KeyError:
        return False
    else:
        return True
  • 对于setdefault(k,d)方法,其在被自定义映射类继承的MutableMapping中的实现依赖于__setitem__方法的具体实现:
def setdefault(self, key, default=None):
    try:
        return self[key]  # 依赖于__getitem__方法的具体实现
    except KeyError:
        self[key] = default  # 依赖于__setitem__方法的具体实现
    return default

3. 自定义映射基类MapBase

虽然本文将分别基于列表实现自定义映射类,后续还将使用哈希表来实现,根据处理哈希碰撞时的不同方式又可以得到不同的具体实现类,因此为了提高代码复用度下面首先定义一个映射基类MapBase

实际上,定义MapBase类的另一个原因是为不同的映射具体实现类表示每一个键值对提供一种统一的方式,即通过组合设计模式使用一个嵌套定义且具有keyvalue两个实例属性的_Item类。

from collections.abc import MutableMapping


class MapBase(MutableMapping):
    """提供用于保存键值对记录类的自定义映射基类"""
    class _Item:

        __slots__ = 'key', 'value'

        def __init__(self, key, value):
            self.key = key
            self.value = value

        def __eq__(self, other):
            return self.key == other.key  # 使用'=='语法基于键比较两个键值对是否相等

        def __ne__(self, other):
            return not (self == other)  # 使用'!='语法基于键比较两个键值对是否不等

        def __lt__(self, other):
            return self.key < other.key  # 使用'<'语法基于键比较两个键值对

4. 列表实现自定义映射类UnsortedListMap

下面通过继承MapBase类,且使用列表保存所有键值对的方式来实现第一个具体映射类UnsortedListMap

__init__

def __init__(self):
    """创建一个空的映射对象"""
    self._table = []  # 映射中的键值对记录保存在列表中

__getitem__

def __getitem__(self, key):
    """返回与键key关联的值value,当键key不存在则抛出KeyError异常"""
    for item in self._table:
        if key == item.key:
            return item.value
    raise KeyError('key error: ', repr(key))

__setitem__

def __setitem__(self, key, value):
    """将key-value添加至映射对象中,当存在键值key时将其值替换为value"""
    for item in self._table:  # 遍历查询映射中是否存在键key
        if key == item.key:  
            item.value = value
            return
    self._table.append(self._Item(key, value))  # 映射中不存在键key

__delitem__

def __delitem__(self, key):
    """删除键key代表的键值对,当键key不存在则抛出KeyError异常"""
    for j in range(len(self._table)):  # 遍历查询映射中是否存在键key
        if key == self._table[j].key:
            self._table.pop(j)
            return
    raise KeyError('key error: ', repr(key))  # 映射中不存在键key

__len__

def __len__(self):
    """返回映射中键值对数量"""
    return len(self._table)

__iter__

def __iter__(self):
    """生成一个映射中所有键的迭代"""
    for item in self._table:
        yield item.key

三、自定义映射类UnsortedListMap效率分析

方法名称时间复杂度
__getitem__() O ( n ) O(n) O(n)
__setitem__() O ( n ) O(n) O(n)
__delitem__() O ( n ) O(n) O(n)
__len__() O ( 1 ) O(1) O(1)
__iter__() O ( n ) O(n) O(n)

四、自定义映射类UnsortedListMap代码测试

# map.py
from collections.abc import MutableMapping


class MapBase(MutableMapping):
    """提供用于保存键值对记录类的自定义映射基类"""
    class _Item:

        __slots__ = 'key', 'value'

        def __init__(self, key, value):
            self.key = key
            self.value = value

        def __eq__(self, other):
            return self.key == other.key  # 使用'=='语法基于键比较两个键值对是否相等

        def __ne__(self, other):
            return not (self == other)  # 使用'!='语法基于键比较两个键值对是否不等

        def __lt__(self, other):
            return self.key < other.key  # 使用'<'语法基于键比较两个键值对


class UnsortedListMap(MapBase):

    def __init__(self):
        """创建一个空的映射对象"""
        self._table = []  # 映射中的键值对记录保存在列表中

    def __getitem__(self, key):
        """返回与键key关联的值value,当键key不存在则抛出KeyError异常"""
        for item in self._table:
            if key == item.key:
                return item.value
        raise KeyError('key error: ', repr(key))

    def __setitem__(self, key, value):
        """将key-value添加至映射对象中,当存在键值key时将其值替换为value"""
        for item in self._table:  # 遍历查询映射中是否存在键key
            if key == item.key:
                item.value = value
                return
        self._table.append(self._Item(key, value))  # 映射中不存在键key

    def __delitem__(self, key):
        """删除键key代表的键值对,当键key不存在则抛出KeyError异常"""
        for j in range(len(self._table)):  # 遍历查询映射中是否存在键key
            if key == self._table[j].key:
                self._table.pop(j)
                return
        raise KeyError('key error: ', repr(key))  # 映射中不存在键key

    def __len__(self):
        """返回映射中键值对数量"""
        return len(self._table)

    def __iter__(self):
        """生成一个映射中所有键的迭代"""
        for item in self._table:
            yield item.key

    def __str__(self):
        """返回映射对象的字符串表示形式"""
        return str(dict(self.items()))


if __name__ == '__main__':
    m = UnsortedListMap()
    print(m)  # {}
    m['K'] = 2
    print(m)  # {'K': 2}
    m['B'] = 4
    print(m)  # {'K': 2, 'B': 4}
    m['U'] = 2
    print(m)  # {'K': 2, 'B': 4, 'U': 2}
    m['V'] = 8
    print(m)  # {'K': 2, 'B': 4, 'U': 2, 'V': 8}
    m['K'] = 9
    print(m)  # {'K': 9, 'B': 4, 'U': 2, 'V': 8}
    print(m['B'])  # 4
    print(m.get('F'))  # None
    print(m.get('F', 5))  # 5
    print(m.get('K', 5))  # 9
    print(len(m))  # 4
    del m['V']
    print(m)  # {'K': 9, 'B': 4, 'U': 2}
    print(m.pop('K'))  # 9
    print(m)  # {'B': 4, 'U': 2}
    print(m.setdefault('B', 1))  # 4
    print(m.setdefault('A', 1))  # 1
    print(m)  # {'B': 4, 'U': 2, 'A': 1}

附录一:MappingMutableMapping源码

1. Mapping源码

class Mapping(Collection):

    __slots__ = ()

    """A Mapping is a generic container for associating key/value
    pairs.

    This class provides concrete generic implementations of all
    methods except for __getitem__, __iter__, and __len__.

    """

    @abstractmethod
    def __getitem__(self, key):
        raise KeyError

    def get(self, key, default=None):
        'D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.'
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        try:
            self[key]
        except KeyError:
            return False
        else:
            return True

    def keys(self):
        "D.keys() -> a set-like object providing a view on D's keys"
        return KeysView(self)

    def items(self):
        "D.items() -> a set-like object providing a view on D's items"
        return ItemsView(self)

    def values(self):
        "D.values() -> an object providing a view on D's values"
        return ValuesView(self)

    def __eq__(self, other):
        if not isinstance(other, Mapping):
            return NotImplemented
        return dict(self.items()) == dict(other.items())

2. MutableMapping源码

class MutableMapping(Mapping):

    __slots__ = ()

    """A MutableMapping is a generic container for associating
    key/value pairs.

    This class provides concrete generic implementations of all
    methods except for __getitem__, __setitem__, __delitem__,
    __iter__, and __len__.

    """

    @abstractmethod
    def __setitem__(self, key, value):
        raise KeyError

    @abstractmethod
    def __delitem__(self, key):
        raise KeyError

    __marker = object()

    def pop(self, key, default=__marker):
        '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
          If key is not found, d is returned if given, otherwise KeyError is raised.
        '''
        try:
            value = self[key]
        except KeyError:
            if default is self.__marker:
                raise
            return default
        else:
            del self[key]
            return value

    def popitem(self):
        '''D.popitem() -> (k, v), remove and return some (key, value) pair
           as a 2-tuple; but raise KeyError if D is empty.
        '''
        try:
            key = next(iter(self))
        except StopIteration:
            raise KeyError from None
        value = self[key]
        del self[key]
        return key, value

    def clear(self):
        'D.clear() -> None.  Remove all items from D.'
        try:
            while True:
                self.popitem()
        except KeyError:
            pass

    def update(self, other=(), /, **kwds):
        ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
            If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
            If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
            In either case, this is followed by: for k, v in F.items(): D[k] = v
        '''
        if isinstance(other, Mapping):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, "keys"):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

    def setdefault(self, key, default=None):
        'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D'
        try:
            return self[key]
        except KeyError:
            self[key] = default
        return default
  • 14
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值