第一章 数据结构和算法

  1. 解压序列,并赋值给多个变量:
    1.1 对任何可迭代对象(list, tuple, dict, str..)都能进行解压,
    解压的意思就是把可迭代对象中的元素都取出来,并且可以进行赋值操作:
    eg1: 元组
    p = (4, 5)
    x, y = p # x = 4, y = 5

     eg2: 列表
         data = ['ACME', 50, 91.1, (2012, 12, 21)]
         name, shares, price, date = data
         # name = 'ACME', date = (2012, 12, 21)
    
     eg3: 序列叠加也没问题
         name, shares, price, (year, mon, day) = data
         #  year = 2012, mon = 12, day = 21
    
     eg4: 
         s = {"a": 1, "b": 2, "c": 3}
         d, e, f = s
         # d = a, b = e, c = f
    
     eg5:
         q = 'Hello'
         a, b, c, d, e = q
         # a = H, ... e = 0
    
     注意上面这种解压方式如果赋值的变量数目和解压出的元素数目不相等会
         报错

    1.2 利用* 来解决解压元素数目多的序列
    variable 可以代指序列中的n多个元素,长度取决于序列长度和已
    赋值变量个数variable 的值一定会返回一个列表
    eg1: 用于列表
    trailing, current = [10, 8, 7, 1, 9, 5, 10, 3]
    # trailing = [10, 8, 7, 1, 9, 5, 10] current = 3

     eg2: 用于字符串
         a = "hello"
         d, e, *f = a
         # d = h, e = e, f = ['l', 'l', 'o']

    1.3 当只需要用加压元素中的某个或某多个时,可以用_来代替想丢弃的元素
    eg1:
    a = "hello"
    d, e, , , _ = a # 丢弃了后三个元素或者*_

  2. python heapq模块 堆数据结构:
    2.1 堆的定义:
    假设有 n 个数据元素的序列 k0,k1,...,kn-1,当且仅当满足 ki≤k2i+1 且 ki≤k2i+2(其中 i=0,2,...,(n-1)/2)时,可以将这组数据称为小顶堆(小根堆);或者满足 ki≥k2i+1 且 ki≥k2i+2(其中 i=0,2,...,(n-1)/2)时,可以将这组数据称为大顶堆(大根堆)。

     对于满足小顶堆的数据序列 k0,k1,...,kn-1,如果将它们顺序排成一棵完全二叉树,则此树的特点是:树中所有节点的值都小于其左、右子节点的值,此树的根节点的值必然最小。反之,对于满足大顶堆的数据序列 k0,k1,...,kn-1,如果将它们顺序排成一棵完全二叉树,则此树的特点是:树中所有节点的值都大于其左、右子节点的值,此树的根节点的值必然最大。
    
                 12              2
             8       10      3       6
           6   4   7    9  6   4    7  9
    
     小顶堆的任意子树也是小顶堆,大顶堆的任意子树还是大顶堆。
    
     Python 提供的是基于小顶堆的操作,因此 Python 可以对 list 中的元素进行小顶堆排列,这样程序每次获取堆中元素时,总会取得堆中最小的元素heap[0]
    
     例如,判断数据序列 9, 30, 49, 46, 58, 79 是否为堆,可以将其转换为一棵完全二叉树,如图 1 所示。
                     9
                 30      49
               46  58   79

    2.2 heapq 模块:
    Python 并没有提供“堆”这种数据类型,它是直接把列表当成堆处理的。Python 提供的 heapq 包中有一些函数,当程序用这些函数来操作列表时,该列表就会表现出“堆”的行为

     heapq 所有函数如下:
         ['heappush', 'heappop', 'heapify', 'heapreplace', 'merge', 'nlargest', 'nsmallest', 'heappushpop']
    
         heappush(heap, item): 将item元素加入堆
         heappop(heap):将堆中最小元素弹出, 并可设置变量接收最小元素返回值
         heapify(heap):将堆属性应用到列表上
         heapreplace(heap, x):将堆中最小元素弹出,并将元素x 入堆,如果推入的
                                 元素将是堆中最小元素,那么也不会弹出刚推入的元素
         heappushpop(heap, item):将item 入堆,然后弹出并返回堆中最小的元素,如果
                                 推入的将是堆中最小元素,那么弹出的将是推入值
         merge(*iterables, key=None, reverse=False)
                                 :将多个有序的堆合并成一个大的有序堆,然后再输出
         nlargest(n, iterable, key=None)
         nsmallest(n, iterable, key=None)
    
         注意只有调用heapify()函数列表才能拥有堆属性
    
         eg:
             from heapq import *
             my_data = list(range(10))
             my_data.append(0.5)
             # 此时my_data依然是一个list列表
             print('my_data的元素:', my_data)
             # 对my_data应用堆属性
             heapify(my_data)
             print('应用堆之后my_data的元素:', my_data)
             heappush(my_data, 7.2)
             print('添加7.2之后my_data的元素:', my_data)
    
             应用堆之后my_data 的元素:[0 , 0.5, 2, 3, 1, 5, 6, 7, 8, 9, 4]
    
             这些元素看上去是杂乱无序的,但其实并不是,它完全满足小顶堆的特征。我们将它转换为完全二叉树:
                                     0
                             0.5         2
                         3       1   5       6
                       7   8   9  4    

    2.3 实现一个队列优先级:
    import heapq

     class PriorityQueue:
    
         def __init__(self):
             self._queue = []
             self._index = 0
    
         def push(self, item, priority):
             heapq.heappush(self._queue, (-priority, self._index, item))
             self._index += 1
    
         def pop(self):
             return heapq.heappop(self._queue)[-1]
    
    
     class Item:
    
         def __init__(self, name):
             self.name = name
    
         def __repr__(self):
             return 'Item({!r})'.format(self.name)
    
     q = PriorityQueue()
     q.push(Item('foo'), 1)
     q.push(Item('bar'), 5)
     q.push(Item('spam'), 4)
     q.push(Item('grok'), 1)
  3. 字典键映射多个值(也叫 multidict):
    一般来说一个键映射多个值有三种方法:
    3.1 利用collections 下的 defaultdict 模块:
    d = defaultdict(list)// 列表方法
    d["a"].append(1)
    d["a"].append(2)
    d["c"].append(1)

         d = defaultdict(set)// 集合方法
         d['a'].add(1)
         d['a'].add(2)
         d['b'].add(4)

    3.2 利用普通字典的setdefault方法:
    e = {}
    e.setdefault("a", {1, 2}).add(3)

     e = []
     e.setdefault("a", [1, 2]).append(3)

    3.3 利用循环:
    d = {}
    for key, value in pairs:
    if key not in d:
    d[key] = []
    d[key].append(value)

  4. 字典排序:
    为 了 能 控 制 一 个 字 典 中 元 素 的 顺 序, 你 可 以 使 用 collections 模 块 中 的OrderedDict 类。在迭代操作的时候它会保持元素被插入时的顺序。

    eg:
    from collections import OrderedDict
    d = OrderedDict()
    d['a']='A'
    d['b']='B'
    d['c']='C'
    for key in d:
    print(key, d[key])

     output: a: A, b:B, c:C

    注意: 原生的dict 并不跟踪字典元素插入顺序,而OrdereDict 会跟踪,原因是OrderDict内部
    维护了一个双向链表,如果使用OrderDict尽量不要在数据多的情况下使用,因为双向链表会
    使内存翻倍。

  5. 字典运算:
    5.1 对字典求最大值,最小值,排序:
    如果对字典的值来进行以上操作的话,需要使用zip函数,将键和值反转过来,然后利用min()
    max(), sorted()来进行运算
    eg:
    prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
    }
    min_prince = min(zip(prices.values(), prices.keys()))
    # (10.75, 'FB')
    min_prince = max(zip(prices.values(), prices.keys()))
    # (612.78, 'AAPL')
    sorted_prince = sorted(zip(prices.values(), prices.keys())) //
    reverse 可选参数

     注意: 执行这些计算的时候,需要注意的是 zip() 函数创建的是一个只能访问一次的迭
         代器, 
         prices_and_names = zip(prices.values(), prices.keys())
         print(min(prices_and_names)) # OK
         print(max(prices_and_name
         s)) # Not Ok

    5.2 如果你在一个字典上执行普通的数学运算,你会发现它们仅仅作用于键,而不是
    值:
    min(prices) # Returns 'AAPL'
    max(prices) # Returns 'IBM'
    可以使用values()和key 来配合使用:
    eg:
    print(min(prices), min(prices, key=lambda k: prices[k]))

  6. 查找两个字典的相同点:
    6.1 为了寻找两个字典的相同点,可以简单的在两字典的 keys() 或者 items() 方法返
    回结果上执行集合操作。比如:
    a = {
    'x': 1,
    'y': 2,
    'z': 3
    }
    b = {
    'w': 10,
    'x': 11,
    'y': 2
    }

             print(a.keys() & b.keys())
             print(a.keys() - b.keys())
             print(a.items() & b.items())
    
     注意: 字典的values()不支持集合操作!!!

    6.2 可用集合操作,来排除字典中的某n项,并生成新的字典:
    c = {key:a[key] for key in a.keys() - {'z', 'w'}}
    # {'y': 2, 'x': 1}

  7. 删除序列的相同元素,并保持原顺序不变:
    7.1 如果序列上的值都是 hashable 类型,那么可以很简单的利用集合和者生成器来解
    决这个问题
    eg1:
    def dedupe(items):
    seen = set()
    for item in items:
    if item not in seen:
    yield item
    seen.add(item)

         a = [1, 5, 2, 1, 9, 1, 5, 10]
         print(list(dedupe(a)))

    7.2 对于非hashable可先转换成hashable再进行操作:
    eg2:
    def dedupe(items, key=None):
    seen = set()
    for item in items:
    val = item if key == None else key(item)
    if val not in seen:
    yield item
    seen.add(val)

         a = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
         b = list(dedupe(a, key=lambda d: (d['x'], d['y'])))
  8. 命名切片:
    当程序出现一大堆无法直视的硬编码下标时,可以利用python中的slice重命名切片
    8.1 内置的 slice() 函数创建了一个切片对象,可以被用在任何切片允许使用的地方
    eg:
    items = [0, 1, 2, 3, 4, 5, 6]
    a = slice(2, 5, 2)
    print(items[a]) # [2, 4]
    items[a] = [100, 200]
    print(items) # [0, 1, 100, 3, 200, 5, 6]
    del items[a]
    print(items) # [0, 1, 3, 5, 6]
    print(a.start, a.stop, a.step) # 2 5 2

    8.2 另外,你还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序
    列上,这个方法返回一个三元组 (start, stop, step), 可避免出现 IndexError 异常
    eg:
    s = "hello world"
    for i in range(*a.indices(len(s))):
    print(s[i]) # l o

  9. 找出序列中出现次数最多的元素:
    9.1 collections.Counter:
    collections.Counter 类就是专门为这类问题而设计的,它甚至有一个有用的
    most_common() 方法直接给了你答案:
    eg1:
    words = [
    'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
    'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
    'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
    'my', 'eyes', "you're", 'under'
    ]
    from collections import Counter
    word_counts = Counter(words)
    # 出现频率最高的 3 个单词
    top_three = word_counts.most_common(3)
    print(top_three)
    # Outputs [('eyes', 8), ('the', 5), ('look', 4)]

     eg2: 可以手动增加元素次数:
         for i in word_counts:
             word_counts[i] += 1
             print(word_counts)
    
     eg3: update()增加元素
         c = Counter(b)
         c.update(['xx', 'yy'])
         print(c)

    9.2 Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合:
    eg:
    a = [...]
    b = [...]
    print(a + b)
    print(a - b) # 类似于集合操作

  10. 通过关键字对字典排序:
    10.1 通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结

    eg1:
        rows = [
            {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
            {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
            {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
            {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
        ]
    
        from operator import itemgetter
        rows_by_fname = sorted(rows, key=itemgetter('fname'))
        rows_by_uid = sorted(rows, key=itemgetter('uid'))
        print(rows_by_fname)
        print(rows_by_uid)
    
    output:
        [{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
        {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
        {'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
        {'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}]
        [{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
        {'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
        {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},
        {'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}]

    10.2 itemgetter() 函数也支持多个 keys,比如下面的代码:
    eg2:
    rows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))
    print(rows_by_lfname)

    outputs:
        [{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},
        {'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},
        {'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},
        {'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}]
    
    注: 对于itemgetter()还可以用lambda来代替:
            eg: key=lambda r: (r['lname'],r['fname'])

    10.3 itemgetter() 对于 min, maax 求最值也适用
    eg: a = min(rows, key=itemgetter('uid'))

  11. 对原生不支持比较的对象排序:
    eg1:
    class User:

            def __init__(self, user_id):
                self.user_id = user_id
    
            def __repr__(self):
                return 'User({})'.format(self.user_id)
    
        users = [User(10), User(20), User(15)]
        print(users)
        a = sorted(users, key=lambda x: x.user_id)
        print(a)
    
    eg2: 用operator.attrgetter()来代替lam   bda
        from operator import attrgetter
        a = sorted(users, key=attrgetter('user_id'))
    
    eg3: min, 和 max 同样适用
  12. 字典字段分组:
    你有一个字典或者实例的序列,然后你想根据某个特定的字段比如 date 来分组迭
    代访问:
    itertools.groupby() 函数对于这样的数据分组操作非常实用:

    eg:
        from operator import itemgetter
        from itertools import groupby
    
        rows = [
            {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
            {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
            {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
            {'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
            {'fname': 'Brian', 'lname': 'Jones', 'uid': 1002},
            {'fname': 'Brian', 'lname': 'Jones', 'uid': 1001},
        ]
    
        rows.sort(key=itemgetter('uid'))
        print(rows)
        for data, item in groupby(rows, key=itemgetter('uid')):
            print(data)
            print(list(item))
    
    注意:
        groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定 key 函数返回
        值相同)的元素序列。在每次迭代的时候,它会返回一个值和一个迭代器对象
  13. 过滤序列元素:
    过滤序列元素有4种实现方法:
    13.1 列表推导式:
    mylist = [1, 4, -5, 10, -7, 2, 3, -1]
    a = [n for n in mylist if n > 0]
    print(a)

    缺点如果列表过于庞大的话,占用内存非常高

    13.2 列表生成器:
    mylist = [1, 4, -5, 10, -7, 2, 3, -1]
    a = (n for n in mylist if n > 0)
    for i in a:
    print(i)

    缺点: 不适合特别复杂的过滤

    13.3 filter() 函数:
    filter() 函数接收两个参数arg1 为函数,arg2 为函数参数
    eg:
    values = [1, 2, 3, 4, "a", "sd", "x-a"]
    def is_int(val):
    try:
    _ = int(val)
    return True
    except ValueError:
    return False

        print(list(filter(is_int, values)))
    
    注意: filter()创建的是一个生成器

    13.4 itertools.compress() 过滤工具:
    和filter()用法类似,只不过这里不再是接收一个函数而是接收一个
    terable对象和一个相对应的 Boolean 选择器序列作为输入参数

    eg:
        from itertools import compress
        addresses = [
            '5412 N CLARK',
            '5148 N CLARK',
            '5800 E 58TH',
            '2122 N CLARK',
            '5645 N RAVENSWOOD',
            '1060 W ADDISON',
            '4801 N BROADWAY',
            '1039 W GRANVILLE',
        ]
        counts = [0, 3, 10, 4, 1, 7, 6, 1]
    
        fil = [n > 5 for n in counts]
        result = list(compress(addresses, fil))
        print(result) 
    
    注意: 返回结果和filter()一样也是一个生成器
  14. 从字典中提取子集:
    两种方法:
    14.1 字典推导:
    prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
    }
    p1 = {key: value for key, value in prices.items() if value > 40}
    p2 = {key: prices[key] for key in prices.keys() if "A" in key}
    14.2 元组实现:
    prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
    }
    p1 = dict((key, value) for key, value in prices.items() if value > 40)

    注意:
        一般用字典推导速度会快一些,同时也比较清晰明了
  15. 映射名称到序列元素:
    问题:
    你有一段通过下标访问列表或者元组中元素的代码,但是这样有时候会使得你的
    代码难以阅读,于是你想通过名称来访问元素?

    解决:
    collections.namedtuple() 函数通过使用一个普通的元组对象来将序列命名化:
    接收两个参数,一个是命名字段,另一个是带命名序列:

    eg:
        from collections import namedtuple
        Subscriber = namedtuple('Subscriber', ['addr', 'joined'])
        sub = Subscriber('[email protected]', '2012-10-19')
        print(sub, sub.addr)
        addr, joined = sub
        print(addr, joined)  # [email protected] 2012-10-19
    
    注意: 命名元组的一个主要用途是将你的代码从下标操作中解脱出来,使结构更清晰。
        另外命名元组还有一个重要的特性就是,作为字典的代替,其特性很像字典,只不过
        字典是通过[key]来访问,而它是通过"."来访问,因为字典储存需要更多的内存,
        所以在构建一个非常大的字典结构时,可以用命名元组来代替
    
        和字典还有一点不同的是,字典是可变的,而命名元组是不可变的,如果非要改
        需要借助元组实例的_replace()方法,它会创建一个新的命名元组。并将对应
        的字段用新的值代替:
    
            eg:
                Stock = namedtuple('Stock', ['name', 'shares', 'price'])
                s = Stock('ACME', 100, 123.45)
                s = s._replace(shares=75)
  16. any()和all()用法:
    16.1 any(iterable) 函数用于判断给定的可迭代参数 iterable 是否全部为
    False,则返回 False,如果有一个为 True,则返回 True
    eg:
    any([0, "", False]) # False
    any([0, "", True]) # True

    16.2 all(iterable) 函数用于判断给定的可迭代参数 iterable 中的所有元素是否都为
    TRUE,如果是返回 True,否则返回 False。

        eg:
            all([0, "", True])  # False
            all([1, "0", True]) # True
  17. 生成器表达式作为参数:
    17.1 生成器表达式作为函数参数时可以省略最外层的括号:
    eg:
    s = sum((x * x for x in nums)) # 显示的传递一个生成器表达式对象
    s = sum(x * x for x in nums) # 更加优雅的实现方式,省略了括号

    17.2 生成器表达式比起创建一个临时列表具有更高效,更省内存,更优雅的优点:
    eg1: 生成器表达式作为参数:
    a = (x for x in range(1, 100000000))
    maxa = max(x * x for x in a)
    print(maxa)

        结果: costtime 12.71s ,内存占用几乎为0
    eg2:
        a = (x for x in range(1, 100000000))
        maxa = max([x * x for x in a])
        print(maxa)
    
        结果: costtime 13.91s 内存占用4.4G
    
    由上可知: 生成器的方式比列表更高效,同时,内存占用更小
  18. 合并多个字典和映射:
    18.1: 如果有多个字典,想要把多个字典整合到一起,然后执行某种统一操作,那么用
    collections 模块中的 ChainMap 类是最简单的方法:
    eg:
    a = {'x': 1, 'z': 3 }
    b = {'y': 2, 'z': 4 }
    from collections import ChainMap
    c = ChainMap(a,b)
    print(c['x']) # Outputs 1 (from a)
    print(c['y']) # Outputs 2 (from b)
    print(c['z']) # Outputs 3 (from a)

    注意:
    
        1: 事实上,一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典
            这些字典并不是真的合并在一起了,ChainMap 类只是在内部创建了
            一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表
            字典的操作对于ChainMap同样适用
    
        2: 如果多个字典整合过程中有存在相同的key,那么只会将首次在字典中出现
            的key进行整合,其他的忽略
    
        3: 对于字典的更新或删除操作总是影响的是列表中第一个字典
    
        eg:
            from collections import ChainMap
    
    
            a = {'x': 1, 'z': 3}
            b = {'y': 2, 'z': 4}
            c = ChainMap(a, b)
            print(c['z'], list(c.keys()))
            del c['x']
            print(a)
            c['w'] = 10
            print(a)

    18.2 update()方法:
    update()方法可以将两个字典合并同时生成新的字典,对于update()生成的字典不
    是对源字典的引用,所以源字典的变动不会影响到合并后的字典,同样合并后的字典
    不会影响到源字典。
    eg:
    a = {'x': 1, 'z': 3}
    b = {'y': 2, 'z': 4}
    c = dict(b)
    print(c)
    b["k"] = 10
    print(c)

猜你喜欢

转载自www.cnblogs.com/limengchun/p/10910973.html