美文网首页
Python 经验 - 数据结构典型用法

Python 经验 - 数据结构典型用法

作者: 千反田爱瑠爱好者 | 来源:发表于2018-09-13 14:26 被阅读6次

    当初能快速上手Python最重要的一点是其内置的数据结构非常强大,关于数据类型的差别(例如可修改对象与不可修改对象)作为基础的知识点在其他教程中已经有非常详细的介绍,所以这里就不再赘述了。这次主要分享一些(也许算是换一种思路的)能提升开发效率的使用心得。

    从列表、字典、集合筛选数据

    我们都知道列表生成式是一种很Pythonic的用法:

    [x for x in data if x>=0]
    

    仅一行代码就能从可迭代对象data中筛选出满足大于等于0条件的元素。
    它的迭代器版本为:

    (x for x in data if x>=0)
    

    区别在于返回的是一个满足筛选条件的迭代器,当遍历时元素在当前循环中才产生所以能大量节省内存,但定义后只能使用一次。

    其实字典和集合也可以使用推导式:

    {k: v for k, v in data.iteritems() if v>=0 }    # iteritems是items的迭代器版本
    {x for x in s if x>=0}
    

    返回的是经过筛选的字典/集合,比使用for循环要简单直接得多。

    • 尽可能使用生成式,效率比较高;
    • 复杂的场景生成式可能会影响可读性,不建议使用。

    为元组元素命名

    有C/C++开发经验的朋友常常会用到:

    // 宏定义
    #define NAME 0
    #define AGE 1
    
    
    // 枚举类型
    enum Student{NAME, AGE}
    

    我想说的是这里用到一种为元素命名的方法,直接通过名称能在顺序结构中以更快的速度访问元素(而不需要遍历整个对象)。
    在Python中也可以通过名称(而不是下标)的方法访问列表/元组元素:
    例如可以自己定义“枚举类型”

    NAME, AGE, SEX, EMAIL = xrange(4)
    student = ("Jim", 16, "male", "Jim418704@gmail.com")
    print(student[NAME], student[AGE], student[SEX], student[EMAIL],)
    

    另外也可以使用collections模块的namedtuple:

    from collections import namedtuple
    
    # 先定义一个名为Student的namedtuple
    Student = namedtuple("Student", ["name", "age", "sex", "email"])
    
    # 然后就可以使用自定义的Student类型创建namedtuple对象啦
    s1 = Student(
        name="Jim",
        age=16,
        sex="male",
        email="Jim418704@gmail.com"
    )
    print(s1.name, s1.age, s1.sex, s1.email)
    

    以上两种方法都可以实现通过名称访问元组/列表的元素哦~

    统计序列中元素的出现频度

    有时我们需要统计列表/元组中某些元素出现的次数。
    其中一种方法是使用字典来统计:

    # 生成范围在1 ~9的元素共100个,并存放在字典中
    li = [random.randrange(1, 10) for i in range(100)]  
    di = dict.fromkeys(li, 0)
    for i in li:
        di[i] += 1
    

    这个时候更方便的方法是使用collections的Counter类型:

    from collections import Counter
    c = Counter(li)
    

    这时候返回的是一个计数器对象,可以使用dict直接转换为以元素为key、次数为value的字典。
    而且还可以直接取出出现次数排名前三的元素,并组成新的字典:

    c.most_common(3)
    

    把字典根据值的大小排序

    先复习一下字典的特性,字典与列表的对比:

    字典 列表
    查找和插入速度快,不会随着键的增加而变慢 查找和插入的时间随着元素增加而增加
    占用大量内存,浪费较多 占用内存少,浪费较多
    通过键(哈希)查询,键不可改变(所以不能用列表等可变类型作为键) 通过下标查询
    默认无序(可以人工排序) 有序

    实际上字典也是可以排序、达到按序查找的效果:
    对于一个随机的字典:

    from random import randint
    d = {x: randint(60, 100) for x in 'abcxyz'}
    

    排序的方法是转化为一个元组列表,再对元组的项排序:

    keys = d.iterkeys()    
    values = d.itervalues()
    # 分别取字典的键和值组成元组(Python3中不支持,只能用keys()、values())
    tu = zip(keys, values)
    new_di = dict(sorted(tu))
    

    更好的方法是利用sorted函数的第二个参数指定参与排序的项:

    dict(sorted(d.items(), key=lambda x: x[1]))
    

    找到多个字典中的公共键

    假设我们有多个字典,需要找到这些字典中都存在的key:

    di_a = {"a": 1, "b": 2, "c": 3}
    di_b = {"a": 5, "d": 2, "e": 3}
    di_c = {"a": -5, "f": 5, "g": 0}
    

    可见公共键就是"a",但在比较大的字典中要迅速找到这个公共键,常见的方法是构造循环遍历di_a,并在循环中判断di_a的键是否同时存在于di_b和di_c......
    有一种更简便的方法:

    di_a.viewkeys() & di_b.viewkeys() & di_c.viewkeys()
    

    di_a.viewkeys()返回的是一个view对象,可以直接转化为存放字典di_a的列表,最重要的优点是view对象是支持集合运算的,上面这个语句返回的是同时存在于di_a、di_b、di_c中的元素(交集)的集合,即三个字典的公共键集合:

    set(['a'])
    

    让字典保持有序

    我们使用字典有时会碰上一种情景,要求让字典一直保持有序(例如比赛中统计名次和成绩),这时候使用collections的OrderDict类型就非常方便了,以下是一个模拟比赛的程序:

    from random import randint
    from time import time
    from collections import OrderedDict
    
    d = OrderedDict()           # 存放选手成绩的有序字典
    players = list('ABCDEFG')   # 模拟七个选手
    
    start = time()
    for i in xrange(8):
        input()     # 阻塞:每次输入回车随机弹出一个选手表示该选手完成比赛
        p = players.pop(randint(0, 7-i))    
        end = time()
        print(i+1, p, end-start)    # 输出名次、选手名、用时
        d[p] = (i+1, end-start)     # 把选手成绩计入有序字典
    
    print()
    for k in d:
        print(k, d[k])       # 根据进入字典的顺序打印元素
    

    实现用户的历史记录功能(最多n条)

    很多时候我们需要让程序实现自动记录用户的历史数据和操作,达到缓存的效果(例如浏览器的搜索记录等)。
    Python的collections模块为我们提供了双向队列,可以很方便的实现这个功能,以下是一个猜数游戏(用户每次输入数字,返回答案的取值范围,随着输入次数增多范围缩小,当用户输入与答案相同的数字时游戏结束):

    from random import randint
    
    N = randint(0, 100)     # 返回0~100范围内的数字
    
    def guess(k):
        if k == N:
            print("Right!")
            return True
        elif k < N:
            print("{} < N...".format(k))
        else:
            print("{} > N...".format(k))
    
    while 1:
        num = input("Please input a number:")
        if num:
            k = int(num)
            if guess(k):
                break
    
    

    但很多时候随着输入的次数增多,就很容易忘记之前已经输入过的数字,这时候可以引入一个存放历史记录的队列:

    from collections import deque
    from pickle import dump, load       # 需要时可用pickle写入文件
    history = deque([], 5)      # 空队列,长度为5
    # ...
    while 1:
        num = input("Please input a number:")
        if num:
            k = int(num)
            history.append(k)
            # 当用户输入?或history时返回历史输入提示
            if num in ['history', '?']: 
                print(list(history))
            if guess(k):
                break
    

    相关文章

      网友评论

          本文标题:Python 经验 - 数据结构典型用法

          本文链接:https://www.haomeiwen.com/subject/dvpwgftx.html