当前位置: 首页 > article >正文

Chapter1:python数据结构与算法

1.python数据结构与算法

本篇博客依托书籍《Python Cookbook》展开对python的学习

1.0 列表、元组、集合、字典

1.1 序列分解为单独变量

问题:我们有一个包含 N 个元素的元组或序列,现在想将它分解为N个单独的变量
解决方案:任何序列(或可迭代的对象)都可以通过一个简单的赋值操作来分解为单独的变量。唯一的要求是变量的总数和结构要与序列相吻合

列表分解序列

list1 = [1, 1, 'a', 3.14] #允许重复、支持不同类型
x,y,z,n = list1
print("列表元素:",list1)
print("第一个元素修改前:", x)
list1[0] = 2              #允许修改
print("第一个元素修改后:", list1[0])
-------------------
列表元素: [1, 1, 'a', 3.14]
第一个元素修改前: 1
第一个元素修改后: 2

元组分解序列

tuple1 = (1, 1, 'a', 3.14) #允许重复、支持不同类型
x,y,z,n = tuple1
print("元组元素:",tuple1)
print("第一个元素修改前:", x)
tuple1[0] = 2 #不允许修改
print("第一个元素修改后:", tuple1[0])
-------------------
元组元素: (1, 1, 'a', 3.14)
第一个元素修改前: 1
Traceback (most recent call last):
  File "~/PycharmProjects/pythonpractice/datastructure.py", line 11, in <module>
    tuple1[0] = 2
TypeError: 'tuple' object does not support item assignment

集合分解序列

set1 = {1, 'a', 3.14}  #不允许重复、支持不同类型
x, y, _ = set1 #分解操作中如想丢弃某些特定值可以选一个用不到的变量名作为要丢弃值的名称,例如:_
print("集合元素:",set1)
print("第一个元素修改前:", x)
set1[0] = 2  #不允许修改
print("第一个元素修改后:", set1[0])
------------------------
集合元素: {1, 3.14, 'a'} #重复元素被删除
第一个元素修改前: 1
Traceback (most recent call last):
  File "/home/wxy/PycharmProjects/pythonpractice/datastructure.py", line 19, in <module>
    set1[0] = 2  #不允许修改
TypeError: 'set' object does not support item assignment

字典分解序列

dict1 = {'a': 1, 'b': 1, 'c': 'a', 'd': 3.14}  # key不允许重复、值可重复、支持不同类型
x, y, z, n = dict1
print("修改前:", x)
dict1['a'] = 2  # 允许修改
print("修改后:", dict1['a'])
--------------------
修改前: a
修改后: 2

集合中数据元素不允许重复、不允许修改
元组中数据元素不允许修改
字典key不允许重复
其他均可重复、可修改

只要对象恰好是可迭代的(可以逐个返回其元素的对象)即只要是可迭代对象就可以执行分解操作
python中的可迭代对象


可迭代对象:字典

dict1 = {'a': 1, 'b': 1, 'c': 'a', 'd': 3.14}  # key不允许重复、支持不同类型
for k in dict1:
    print(k,dict1[k])
------------------
a 1
b 1
c a
d 3.14

可迭代对象:文件对象

file_path = '/home/..../Downloads/command.txt'
with open(file_path, 'r') as file:
    for line in file:
        print(line, '\n')
------------------
aaaabbbb

可迭代对象:生成器(使用yield函数,返回一个迭代器,可以逐个生成值)

def my_generator():
    yield 'a'
    yield 2
    yield 3.14


for value in my_generator(): # def my_generator() -> Generator[str | int | float, Any, None]
    print(value)
---------------------
a
2
3.14

可迭代对象:范围对象(range() 函数返回一个可迭代的对象,可以用于生成整数序列)

for i in range(1, 6, 1):
    print(i)
---------------------
1
2
3
4
5

可迭代对象:numpy数组

import numpy as np
# arr = [1, 2, 3]
# n_arr = np.array(arr)
n_arr = np.array([1, 2, 3])
for i in n_arr:
    print(i)
-------------------------
1
2
3    

可迭代对象:自定义可迭代对象

class MyInterable: #定义一个名为 MyIterable 的类
    def __init__(self): #定义类的构造函数,当实例化该类时自动调用
        self.data=[1,2,3] #在构造函数中定义一个列表属性 data,其中包含元素 [1, 2, 3]
    def __iter__(self): #使对象成为一个可迭代对象
        self.index = 0; #初始化一个索引变量 index 为 0,用于指示当前迭代的位置
        return self #返回自身实例,使得 MyIterable 类对象可以用于 for 循环等迭代操作
    def __next__(self): #使该对象支持逐步返回元素。
        if self.index < len(self.data): #判断当前索引是否在 data 列表长度范围内,确保不会越界。
           result = self.data[self.index] #取得 data 列表中当前位置 index 处的元素并存入
           self.index += 1 #将 index 递增1,为下一次迭代做准备
           return result #返回当前位置的元素值 result
        else:
           raise StopIteration #如果索引超出范围,抛出 StopIteration 异常,通知 Python 结束迭代

for item in MyInterable(): #使用 for 循环迭代 MyIterable 类的实例,并将每个返回的元素赋值给 item
    print(item) #print(item) #将每个元素 item 输出到控制台
-----------------------
1
2
3

1.2 从任意长度的可迭代对象中分解元素

问题:需要从某个可迭代对象中分解出 N 个元素,但是这个可迭代对象的长度可能超过 N,这会导致出现“分解的值过多(too many values to unpack)”的异常
解决方案:Python 的 *表达式 可以用来解决这个问题

假设有一些用户记录,记录由姓名和电子邮件地址组成,后面跟着任意数量的电话号码。则可以像这样分解记录:

record = ['Irving', 'your@gmail.com', '725-123', '653-1234']
name, mail, *telphone = record
print(name)
print(mail)
print(*telphone)
----------------------
Irving
your@gmail.com
725-123 653-1234

特定的字符串拆分

line = 'nobody:*:-2:-2:Unprivileged:User:/var/empty:/usr/bin/false'
uname, *field, home_dir,sh = line.split(':')
print(uname)
print(*field)
print(home_dir)
print(sh)
-------------------
nobody
* -2 -2 Unprivileged User
/var/empty
/usr/bin/false

1.3 保存最后N个元素

问题:对一系列文本行做简单的文本匹配操作,当发现有匹配时就输出当前的匹配行以及最后检查过的N行文本
解决方案:有限长度的队列

from collections import deque
'''
def search(lines: {__iter__},
           pattern: Any,
           history: Any) -> Generator[tuple[Any, deque], Any, No
'''
def search(lines, pattern, history):
    previous_lines = deque(maxlen=history) #deque(maxlen=N)创建了一个固定长度的队列。当有新记录加入而队列已满时会自动移除最旧的那条记录
    for line in lines:  # 每行逐个进队列
        if pattern in line:  # 该行包含pattern字符串
            yield line, previous_lines  # 生成该行和队列内容(历史记录)
        previous_lines.append(line)  # 将该行添加到队列


if __name__ == '__main__' :
    with open('/home/irving/Downloads/command.txt') as f: #`f` 是文件对象的变量名。这行代码使用open函数打开文件,并将文件对象赋值给变量 `f`。在 `with` 语句块中,可以使用 `f` 来读取或操作文件内容。`with` 语句确保在代码块执行完毕后,文件会被正确关闭。
        for line, prelines in search(f, 'build', 5): 
            for pline in prelines:  # 打印队列内容(历史记录)
                print(pline, end='')
            print(line, end='')  # 打印匹配行
            print('-'*20)

1.4 找到最大或最小的N个元素

问题:在某个集合中找出最大或最小的N个元素
解决方案:heapq 模块的 nlargest 和 nsmallest 函数

import heapq

portfolio = [
    {'name': 'IBM', 'shares': 100, 'price': 91.1},
    {'name': 'AAPL', 'shares': 50, 'price': 543.22},
    {'name': 'FB', 'shares': 200, 'price': 21.09},
    {'name': 'HPQ', 'shares': 35, 'price': 31.75},
    {'name': 'YHOO', 'shares': 45, 'price': 16.35},
    {'name': 'ACME', 'shares': 75, 'price': 115.65}
]
'''
1. 第一个`s`是`lambda`表达式的参数。`lambda s: s['price']`定义了一个匿名函数,这个函数接受一个参数`s`,并返回`s`的`price`属性。
2. 第二个`s`是`lambda`表达式内部的变量,表示传递给匿名函数的每个`portfolio`中的元素(即每个字典)。
'''
# `heapq.nsmallest`函数使用这个`lambda`函数来比较`portfolio`中每个元素的`price`属性,以找到价格最便宜的三个元素
cheap = heapq.nsmallest(3, portfolio, lambda s: s['price'])
print(cheap)
expensive = heapq.nlargest(2, portfolio, lambda s: s['price'])
print(expensive)
---------------------------------
[{'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}]
[{'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}]

情况一:如果只是简单地想找到最小或最大的元素(N=1时),那么用min()和max()会更加快
情况二:当所要找的元素数量相对较小时,函数nlargest()和nsmallest()才是最适用的
情况三:如果要找的元素数量N和集合本身的大小差不多大,通常更快的方法是先对集合排序,然后做切片操作(例如,使用sorted(items)[:N]或者sorted(items)[-N:])

补充lambda

#补充lambda知识
def fun1(x,y):
    return x+y

fun2 = lambda x,y: x+y
# lambda简单功能的匿名函数 lambda 输入参数: 返回值
print(fun1(1,2))
print(fun2(1,2))
==============
3
3

1.5 实现优先级队列

问题:我们想要实现一个队列,它能够以给定的优先级来对元素排序,且每次pop操作时都会返回优先级最高的那个元素
解决方案:优先级队列

#实现一个队列,它能够以给定的优先级来对元素排序,且每次pop操作时都会返回优先级最高的那个元素。
import heapq

'''
在 Python 中,加下划线通常有以下几种含义:
1. **单下划线前缀 (`_variable`)**:表示这是一个内部变量或方法,建议不要在类或模块外部使用。
2. **双下划线前缀 (`__variable`)**:触发名称改写(name mangling),用于避免子类覆盖,通常用于类的私有属性。
3. **单下划线后缀 (`variable_`)**:避免与 Python 关键字冲突。
4. **单独的下划线 (`_`)**:在交互式解释器中,表示上一个表达式的结果;在循环或解包中,表示一个临时变量或不需要的值。
5. **双下划线前后缀 (`__variable__`)**:表示特殊方法或属性,通常由 Python 内部使用,如 `__init__`、`__str__` 等。
`self._queue` 和 `self._index` 使用了单下划线前缀,表示这些属性是内部使用的,不建议在类外部直接访问。
'''


class PriorityQueue:
    def __init__(self):  # 双下划线前后缀表示特殊方法或属性,通常由Python内部使用
        self._queue = []  # 单下划线前缀是一个内部变量或方法,建议不要在类或模块外部使用
        self._index = 0

    def push(self, item, priority):
    # 把priority取负值是为了让队列能够按元素的优先级从高到低的顺序排列
    # 变量index的作用是为了将具有相同优先级的元素以适当的顺序排列,通过维护一个不断递增的索引,元素将以它们入队列时的顺序来排列
        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):
        # {!r}` 是格式化字符串的一部分,用于调用对象的 `__repr__` 方法。
        # `__repr__` 方法返回一个对象的“官方”字符串表示,通常可以用来重新创建这个对象
        # `{!r}` 会将 `self.name` 的 `__repr__` 表示插入到字符串中
        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)
#循环pop队列元素
for i in range(4):
    print(f"第{i+1}次出队元素:{q.pop()}")

-------------------------1次出队元素:Item('bar')2次出队元素:Item('spam')3次出队元素:Item('foo')4次出队元素:Item('grok')
# 拥有相同优先级的两个元素(foo和grok)返回的顺序同它们插入到队列时的顺序相同。
# 如果想将这个队列用于线程间通信,还需要增加适当的锁和信号机制

1.6 在字典中将键映射到多个值上

如果想让键映射到多个值,需要将这多个值保存到另一个容器如列表或集合中
如果希望保留元素插入的顺序,就用列表。
如果希望消除重复元素(且不在意它们的顺序),就用集合

from collections import defaultdict
d = defaultdict(list) # d = defaultdict(set)
d['a'].append(1)
d['a'].append(2)
d['a'].append(3)
d['b'].append(4)
d['b'].append(5)
print(d)
------------------
defaultdict(<class 'list'>, {'a': [1, 2, 3], 'b': [4, 5]})

1.7 让字典保持有序

问题:我们想创建一个字典,同时当对字典做迭代或序列化操作时,也能控制其中元素的顺序
解决方案:要控制字典中元素的顺序,可以使用collections模块中的OrderedDict类。当对字典做迭代时,它会严格按照元素初始添加的顺序进行
OrderedDict内部维护了一个双向链表,它会根据元素加入的顺序来排列键的位置。第一个新加入的元素被放置在链表的末尾。接下来对已存在的键做重新赋值不会改变键的顺序。

from collections import OrderedDict
d = OrderedDict()
d['foo'] = 2
d['bar'] = 1
d['spam'] = 3
d['grok'] = 4
for key in d:
    print(key, d[key])
-------------------------
# 严格按照添加元素的顺序进行输出
foo 2
bar 1
spam 3
grok 4
-------------------------
#精确控制json文件中字段顺序
import json
json.dumps(d)

OrderedDict的大小是普通字典的2倍多,这是由于它额外创建的链表所致。根据具体需求判断使用OrderedDict所带来的好处是否能超越因额外的内存开销所带来的缺点

1.8 与字典相关的计算

问题:字典上对数据执行各式各样的计算(比如求最小值、最大值、排序等)

prices = {
'ACME': 45.23,
'AAPL': 612.78,
'IBM': 205.55,
'HPQ': 37.20,
'FB': 10.75
}
# 根据"键"的大小比较,结果显然不对
print('股票最低价:', min(prices))
print('股票最高价:', max(prices))
print('-'*20)
=====================
股票最低价: AAPL
股票最高价: IBM
--------------------

# 根据"值"的大小比较,但只显示值而不显示键
print('股票最低价:', min(prices.values()))
print('股票最高价:', max(prices.values()))
print('-'*20)
=====================
股票最低价: 10.75
股票最高价: 612.78
--------------------

# 根据"值"的大小比较后显示对应的"键"
'''
`lambda` 表达式用于创建匿名函数。它的语法是 `lambda 参数: 表达式`。
`lambda k: prices[k]` 创建了一个匿名函数,该函数接受一个参数 `k`,并返回 `prices[k]` 的值。
这个匿名函数被用作 `min` 和 `max` 函数的 `key` 参数,用于根据字典 `prices` 中的值来比较键。
具体来说:
- `min(prices, key=lambda k: prices[k])` 找到 `prices` 字典中值最小的键。
- `max(prices, key=lambda k: prices[k])` 找到 `prices` 字典中值最大的键。
'''
# 使用 lambda 表达式根据字典的值进行比较,并显示对应的键。
# k为键,prices[k]为值,用值进行比较返回键
print('股票最低价:', min(prices, key=lambda k: prices[k]))
print('股票最高价:', max(prices, key=lambda k: prices[k]))
print('-'*20)
=====================
股票最低价: FB
股票最高价: AAPL
--------------------

# 使用 lambda 表达式根据字典的值进行比较,得到对应的键。最外层prices[键]得到值
print('股票最低价:', prices[min(prices, key=lambda k: prices[k])])
print('股票最高价:', prices[max(prices, key=lambda k: prices[k])])
print('-'*20)
=====================
股票最低价: 10.75
股票最高价: 612.78
--------------------

# zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表
# 为了能对字典内容做些有用的计算,通常会利用zip()将字典的键和值反转过来
# 将字典的键值对反转为值键的目的是为了能够根据值来进行比较和排序。通过使用 `zip()` 函数,可以将字典的键和值反转过来,
# 从而方便地找到值最小或最大的键。这样做的好处是可以直接使用 `min()` 和 `max()` 函数来获取对应的键和值。
print('股票最低价:', min(zip(prices.values(), prices.keys())))
print('股票最高价:', max(zip(prices.values(), prices.keys())))
print('-'*20)
=========================
股票最低价: (10.75, 'FB')
股票最高价: (612.78, 'AAPL')
--------------------


# 要对数据排序只要使用zip()再配合sorted()即可
sort_dict = sorted(zip(prices.values(), prices.keys()))
print(sort_dict)
print(min(sort_dict))
print(max(sort_dict))
print('-'*20)
=========================
[(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')]
(10.75, 'FB')
(612.78, 'AAPL')
--------------------

#注意zip()创建了一个迭代器,它的内容只能被消费一次
price_name = zip(prices.values(), prices.keys())
print(min(price_name))  # zip只能被用一次
print(max(price_name))  # ValueError: max() arg is an empty sequence
=========================
(10.75, 'FB')
Traceback (most recent call last):
  File "/home/irving/PycharmProjects/pythonpractice/datastructure.py", line 260, in <module>
    print(max(price_name))
ValueError: max() arg is an empty sequence

1.9 在两个字典中寻找相同点

问题:有两个字典,我们想找出它们中间可能相同的地方(相同的键、相同的值等)

a = {
    'x': 1,
    'y': 2,
    'z': 3
}
b = {
    'w': 10,
    'x': 11,
    'y': 2
}
# 字典a和字典b相同的键
print(a.keys() & b.keys())
# 字典a中有但字典b中没有的关键字
print(a.keys() - b.keys())
# 字典a和字典b中相同键值对
print(a.items() & b.items())
# 把字典a中键z和w过滤生成新字典
#`a.keys() - {'z'}`:这是一个集合操作,表示从字典 `a` 的键集合中去掉键 `'z'`。
#`{key: a[key] for key in ...}`:这是一个字典推导式,用于生成一个新的字典。
# 它遍历前面集合操作得到的键集合,并将每个键对应的值从字典 `a` 中取出,构成新的键值对。
new_dict = {key: a[key] for key in a.keys() - {'z'}}
print(new_dict)
======================
{'y', 'x'}
{'z'}
{('y', 2)}
{'y': 2, 'x': 1}

(1)keys()方法会返回keys-view对象,其中暴露了所有的键,
如果需要对字典的键做常见的集合操作,那么就能直接使用keys-view对象而不必先将它们转化为集合
(2)字典的values()方法并不支持集合操作。
部分原因是因为在字典中键和值是不同的,从值的角度来看并不能保证所有的值都是唯一的。单这一条原因就使得某些特定的集合操作是有问题的。但是,如果必须执行这样的操作,还是可以先将值转化为集合来实现
(3)字典的items()方法返回由(key,value)对组成的items-view对象。
这个对象支持类似的集合操作,可用来完成找出两个字典间有哪些键值对有相同之处的操作。

综上:字典中的键和键值对是唯一的可以使用集合操作,但字典中的值不一定唯一,需要将值转为集合才能使用集合操作(集合不允许有重复元素)

1.10 从序列中移除重复项且保持元素间顺序不变

问题:我们想去除序列中出现的重复元素,但仍然保持剩下的元素顺序不变。
解决方案:如果序列中的值是可哈希(hashable)的,那么这个问题可以通过使用集合和生成器轻松解决

“可哈希”意思是对象一旦创建,它的内容不可修改,该对象对应一个固定的哈希值,这个值在对象的生命周期内不会改变


如果想要做的只是去除重复项,而不考虑处理后元素的顺序,那么通常足够简单的办法就是构建一个集合

a = [1, 5, 2, 1, 9, 1, 5, 10]
print(set(a))
====================
{1, 2, 5, 9, 10}

可哈希对象(创建后不可修改)情况下从序列中移除重复项且保持元素间顺序不变

# 从序列中移除重复项且保持元素间顺序不变
def dedupe(items):
    # 创建集合用于存储items中非重复元素
    seen = set()
    # 遍历items
    for item in items:
        # 如果集合中没有出现过item则生成item并加入集合
        if item not in seen:
            yield item
            seen.add(item)

# 列表中的元素为整数,可哈希(定义后不可被修改)
a = [1, 5, 2, 1, 9, 1, 5, 10]
#直接检查列表中元素是否有重复,若有则排除
print(list(dedupe(a)))

不可哈希对象(创建后能够被修改)情况下从序列中移除重复项且保持元素间顺序不变
如果希望在一个较复杂的数据结构中,只根据对象的某个字段或属性来去除重复项,可以采用这种方式

def dedupe(items, key=None):
    seen = set()
    for item in items:  # item:{'x':1, 'y':2 }
        # 如果 `key` 参数为 `None`,则 `val` 直接等于 `item`。
        # 如果 `key` 参数不为 `None`,则 `val` 等于 `key(item)` 的结果
        # 根据是否提供了 `key` 函数来决定如何处理 `item`。
        # 如果提供了 `key` 函数,则使用 `key` 函数处理后的值作为 `val`,否则直接使用 `item` 作为 `val`。
        val = item if key is None else key(item)  # val:(1,2) 如果key不为none就调用lambda函数返回(d['x'],d['y'])
        if val not in seen:  # 通过检查集合中是否存在值(a,b)排除items中的重复项
            yield item  # item:{'x':1, 'y':2 } 生成值相应的item添加到list中
            seen.add(val)  # seen{(1,2)}

#列表中元素为字典,不可哈希(定义后可被修改)
a = [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
# 提取出字典中的值,检查值是否重复,若有重复则排除相应的键值对
print(list(dedupe(a, key=lambda d: (d['x'], d['y']))))
print(list(dedupe(a, key=lambda d: d['x'])))

1.11 对切片命名

items = [0, 1, 2, 3, 4, 5, 6]
print(items[2:4])
a = slice(2, 4)  # 下标2到3(不包括4)
print(items[a])
items[a] = [10, 11]  # 替换切片内容
print(items)
del items[a]  # 删除切片内容
print(items)
==================
[2, 3]
[2, 3]
[0, 1, 10, 11, 4, 5, 6]
[0, 1, 4, 5, 6]

1.12 找出序列中出现次数最多的元素

问题:我们有一个元素序列,想知道在序列中出现次数最多的元素是什么
解决方案:模块中的Counter类正是为此类问题所设计的。它甚至有一个非常方便的most_common()方法可以直接告诉我们答案

from collections import Counter
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'
]
# Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
word_counts = Counter(words)
top_three = word_counts.most_common(3)
print(top_three)
print("添加单词前:", word_counts)

morewords = ['why','are','you','not','looking','in','my','eyes']
word_counts.update(morewords) # 添加单词并计数
print(words)
print("添加单词后:", word_counts)
================================
[('eyes', 8), ('the', 5), ('look', 4)]
添加单词前: Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
['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']
添加单词后: Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})
================================
# Counter对象的数学运算
a = Counter(words)
print("words列表计数:", a)
b = Counter(morewords)
print("morewords列表计数:", b)
c = a + b
print("计数相加:", c)
d = a - b  # 从a中剔除与b中相同的内容
print("计数相减:", d)
===============================
words列表计数: Counter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2, 'not': 1, "don't": 1, "you're": 1, 'under': 1})
morewords列表计数: Counter({'why': 1, 'are': 1, 'you': 1, 'not': 1, 'looking': 1, 'in': 1, 'my': 1, 'eyes': 1})
计数相加: Counter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1, 'why': 1, 'are': 1, 'you': 1, 'looking': 1, 'in': 1})
计数相减: Counter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2, "don't": 1, "you're": 1, 'under': 1})

1.13 通过公共键对字典列表排序

问题:我们有一个字典列表,想根据一个或多个字典中的值来对列表排序
解决方案:利用operator模块中的itemgetter函数对这类结构进行排序是非常简单的

from operator import itemgetter
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}
]
rows_by_fname = sorted(rows, key=itemgetter('fname')) # 根据fname字段对字典元素进行排序
rows_by_uid = sorted(rows, key=itemgetter('uid'))
rows_by_lname_fname = sorted(rows, key=itemgetter('lname','fname'))
print(rows_by_fname)
print(rows_by_uid)
print(rows_by_lname_fname)
print(min(rows, key=itemgetter('uid'))) # 根据uid字段对字典元素进行排序,提取出uid最小的字典元素
print(max(rows, key=itemgetter('uid')))
======================================
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}, {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}

1.14 对不原生支持比较操作的对象排序

问题:我们想在同一个类的实例之间做排序,但是它们并不原生支持比较操作
解决方案:使用built-in sorted函数

'''
1. `class User:` 定义了一个名为 `User` 的类。
2. `def __init__(self, user_id):` 是类的构造函数(初始化方法),在创建 `User` 对象时被调用。它接受一个参数 `user_id`。
3. `self.user_id = user_id` 将传入的 `user_id` 参数赋值给实例变量 `self.user_id`,以便在类的其他方法中使用。
4. `def __repr__(self):` 定义了类的 `__repr__` 方法,该方法返回对象的字符串表示,通常用于调试和开发。
5. `return 'User({})'.format(self.user_id)` 返回一个格式化的字符串,显示 `User` 对象的 `user_id`。
'''
class User:
    def __init__(self, user_id):
        self.user_id = user_id

    def __repr__(self):
        return 'User({})'.format(self.user_id)


users = [User(23), User(3), User(99)]
print(users)
print(sorted(users, key=lambda u: u.user_id))
#或者不使用lambda,而使用key=attrgetter('user_id')
#sorted(users, key=attrgetter('user_id'))
===============================
[User(23), User(3), User(99)]
[User(3), User(23), User(99)]

1.15 根据字段将记录分组

问题:有一系列的字典或对象实例,我们想根据某个特定的字段(比如说日期)来分组迭代数据
解决方案:函数在对数据进行分组时特别有用

'''
- `itemgetter`:来自 `operator` 模块,用于从对象(如列表、元组、字典)中提取特定项。它返回一个函数,该函数可以用作 `sorted`、`min`、`max` 等函数的 `key` 参数,以便根据特定项进行排序或比较。
- `groupby`:来自 `itertools` 模块,用于对数据进行分组。它返回一个迭代器,生成键和组的子迭代器。数据需要先按分组键排序,然后才能正确分组。
'''
from operator import itemgetter
from itertools import groupby

rows = [
    {'address': '5412 N CLARK', 'date': '07/01/2012'},
    {'address': '5148 N CLARK', 'date': '07/04/2012'},
    {'address': '5800 E 58TH', 'date': '07/02/2012'},
    {'address': '2122 N CLARK', 'date': '07/03/2012'},
    {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
    {'address': '1060 W ADDISON', 'date': '07/02/2012'},
    {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
    {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
]
# 根据关键字date进行排序
rows.sort(key=itemgetter('date'))
# 根据date内容进行分组
'''
`items` 是一个迭代器,它包含了在 `groupby` 分组操作中与当前 `date` 相对应的所有字典元素。
每次循环时,`groupby` 会根据 `date` 键的值将 `rows` 列表中的元素分组,并生成一个键值对,
其中键是 `date`,值是一个包含所有具有相同 `date` 值的字典元素的迭代器。
'''
for date, items in groupby(rows, key=itemgetter('date')):
    print(date)
    for i in items:
        print(' ', i)
===========================
07/01/2012
  {'address': '5412 N CLARK', 'date': '07/01/2012'}
  {'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
  {'address': '5800 E 58TH', 'date': '07/02/2012'}
  {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
  {'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
  {'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
  {'address': '5148 N CLARK', 'date': '07/04/2012'}
  {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}

1.16 筛选序列中的元素

问题:序列中含有一些数据,我们需要提取出其中的值或根据某些标准对序列做删减。
解决方案:要筛选序列中的数据,通常最简单的方法是使用列表推导式生成器表达式

列表推导式生成一个完整的列表,适合在不太大的数据集上使用,因为整个列表会存储在内存中
生成器表达式生成一个生成器对象,按需产生每个元素,适合在大型数据集或内存紧张的情况下使用

列表推导式是一种在一行代码中创建列表的方式,通常包括一个表达式、一个循环和一个可选的条件

[expression for item in iterable if condition]
expression:应用于每个元素的表达式,可以是对元素的运算或操作。
item in iterable:遍历的迭代对象。
condition:可选条件,筛选满足条件的元素。

我们有一个列表 [1, 2, 3, 4, 5, 6],希望筛选出其中的偶数,并将每个偶数平方,放入新的列表中

mylist = [1, 2, 3, 4, 5, 6]
square_even = [n**2 for n in mylist if n%2==0]
print(square_even)
============
[4, 16, 36]

生成器表达式与列表推导式类似,只不过它使用圆括号 (),而不是方括号 []。生成器表达式不会一次性生成整个结果列表,而是按需生成每一个元素。这使得它在处理大数据集时更加高效。

(expression for item in iterable if condition)

假设我们还是要筛选偶数的平方,但不需要将结果保存为列表,而是直接对平方后的偶数进行求和

mylist1 = [1, 2, 3, 4, 5, 6]
square_even_sum = sum(n**2 for n in mylist1 if n % 2 == 0)
print(square_even_sum)
===========
56

使用列表推导式的一个潜在缺点是如果原始输入非常大的话,这么做可能会产生一个庞大的结果。如果这是你需要考虑的问题,那么可以使用生成器表达式通过迭代的方式产生筛选的结果。

numbers = [-1, 2, -3, 4, 5, 6, 8, 9, 10]
pos = (n for n in numbers if n > 0)
for x in pos:
    print(x)
==================
2
4
5
6
8
9
10    

筛选值并将不符合条件的值进行重新赋值

mylist = [1, 4, -5, 10, -7, 2, 3, -1]
clip_seg = [n if n > 0 else 0 for n in mylist]
print(clip_seg)
=================
[1, 4, 0, 10, 0, 2, 3, 0]

非简单筛选
有时候筛选的标准没法简单地表示在列表推导式或生成器表达式中。
比如,假设筛选过程涉及异常处理或者其他一些复杂的细节。
基于此,可以将处理筛选逻辑的代码放到单独的函数中,然后使用内建的filter()函数处理

筛选出列表中整数值

values = ['1', '2', '-3', '-', '4', 'N/A', '5']


def is_int(val):
    try:
        x = int(val)
        return True
    except ValueError:
        return False


ivals = list(filter(is_int, values))
print(ivals)
===============
['1', '2', '-3', '4', '5']

如果想把对一个序列的筛选结果施加到另一个相关的序列上时,使用筛选工具itertools.compress(),
该工具它接受一个可迭代对象以及一个布尔选择器序列作为输入。
输出时,它会给出所有在相应的布尔选择器中为True的可迭代对象元素。

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]
# 创建一个布尔序列,用来表示哪个元素可满足我们的条件
more5 = [n > 5 for n in counts]
print(more5)
# compress()函数挑选出满足布尔值为True的相应元素
print(list(compress(addresses, more5)))
=================
[False, False, True, False, False, True, True, False]
['5800 E 58TH', '4801 N BROADWAY', '1039 W GRANVILLE']

1.17 从字典中提取子集

问题:我们想创建一个字典,其本身是另一个字典的子集。
解决方案:利用字典推导式

prices = {
    'ACME': 45.23,
    'AAPL': 612.78,
    'IBM': 205.55,
    'HPQ': 37.20,
    'FB': 10.75
}
# Make a dictionary of all prices over 200
p1 = {key: value for key, value in prices.items() if value > 200}
print("all prices over 200:", p1)
# Make a dictionary of tech stocks
tech_names = { 'AAPL', 'IBM', 'HPQ', 'MSFT' }
p2 = {key: value for key, value in prices.items() if key in tech_names}
print("tech stocks:", p2)
====================
all prices over 200: {'AAPL': 612.78, 'IBM': 205.55}
tech stocks: {'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}

1.18 将名称映射到序列的元素中

问题:我们的代码是通过位置(即索引,或下标)来访问列表或元组的,但有时候这会使代码变得有些难以阅读。
我们希望可以通过名称来访问元素,以此减少结构中对位置的依赖性。
解决方案:collections.namedtuple() 给它一个类型名称以及相应的字段,它就返回一个可实例化的类、为你已经定义好的字段传入值等

point = (10, 20)
x = point[0]
y = point[1]
print(f"通过下标访问x坐标:{x}, y坐标:{y}")
=================
通过下标访问x坐标:10, y坐标:20
from collections import namedtuple
# 定义namedtuple对象
Point = namedtuple('Point', ['x', 'y'])
# 实例化对象
point = Point(10, 20)
x = point.x
y = point.y
print(f"通过字段访问x坐标:{x}, y坐标:{y}")
# 尽管namedtuple的实例看起来就像一个普通的类实例,
# 但它的实例与普通的元组是可互换的,而且支持所有普通元组所支持的操作,例如索引(indexing)和分解(unpacking)
print(len(point))
x, y = point
print(f"unpacking结果x坐标:{x}, y坐标:{y}")
# point.x = 15 AttributeError: can't set attribute
# 如果需要修改任何属性,可以通过使用namedtuple实例的_replace()方法来实现。
# 该方法会创建一个全新的命名元组,并对相应的值做替换
point = point._replace(x=15)
print(point)
===============
通过字段访问x坐标:10, y坐标:20
2
unpacking结果x坐标:10, y坐标:20
Point(x=15, y=20)

1.19 同时对数据做转换和换算

问题:我们需要调用一个换算(reduction)函数(例如sum()、min()、max()),但首先得对数据做转换或筛选。
(1)数据转换或筛选、(2)数据换算
解决方案:在函数参数中使用生成器表达式(将数据换算和转换结合在一起)

nums = [1, 2, 3, 4, 5]
# 生成器表达式完成数据转换,直接将转换后的数据作为sum函数的参数进行计算
# s = sum((x * x for x in nums))
# s = sum(x * x for x in nums) 这两行代码表示的是同一个意思
result = sum(n*n for n in nums)
print(result)
========
55

1.20 将多个映射合并为单个映射

问题:我们有多个字典或映射,想在逻辑上将它们合并为一个单独的映射结构,以此执行某些特定的操作,比如查找值或检查键是否存在。
解决方案:使用collection中的ChainMap

from collections import ChainMap
a = {'x': 1, 'z': 3 }
b = {'y': 2, 'z': 4 }
# 合并两个字典
c = ChainMap(a, b)
print(c['x'])
print(c['y'])
print(c['z'])  # 如果有重复的键,那么这里会采用第一个映射中所对应的值
len(c)
print(list(c.keys()))
print(list(c.values()))
# 修改映射的操作总是会作用在列出的第一个映射结构上
c['z'] = 10
c['w'] = 40
print(c)
del c['x']
print(c)
===============
1
2
3
['y', 'z', 'x']
[2, 3, 1]
ChainMap({'x': 1, 'z': 10, 'w': 40}, {'y': 2, 'z': 4})
ChainMap({'z': 10, 'w': 40}, {'y': 2, 'z': 4})

ChainMap 与带有作用域的值,比如编程语言中的变量(即全局变量、局部变量等)一起工作时特别有用
通过 ChainMap 的 new_child() 方法模拟了变量的作用域层级,类似于在编程语言中不同作用域中的变量解析规则。这里的代码具体演示了在多个作用域之间的变量“遮蔽”关系。
ChainMap 模拟了多层作用域中的变量查找机制:

  1. 新增作用域使用 new_child()
  2. 丢弃作用域使用 parents
  3. 多层作用域的变量遮蔽规则,即如果有重复的键,总是取最上层映射中对应的值。
from collections import ChainMap
values = ChainMap()
values['x'] = 1
# 添加新映射
values = values.new_child()
values['x'] = 2
values = values.new_child()
values['x'] = 3
print(values)
print(values['x'])
# 抛弃最新的一个映射
values = values.parents
print(values)
print(values['x'])  # 如果有重复的键,那么这里会采用第一个映射中所对应的值
=================
ChainMap({'x': 3}, {'x': 2}, {'x': 1})
3
ChainMap({'x': 2}, {'x': 1})
2

http://www.kler.cn/a/388946.html

相关文章:

  • 2024年11月13日
  • [Docker#8] 容器配置 | Mysql | Redis | C++ | 资源控制 | 命令对比
  • 机器学习——贝叶斯
  • 10款翻译工具实践体验感受与解析!!!!!
  • 词嵌入方法(Word Embedding)
  • Ollama的安装以及大模型下载教程
  • 解耦与模块化:鸿蒙平台上的服务注册与查找机制
  • 【Ubuntu】ubuntu 22.04 设置 Xorg 弃用 Wayland
  • webstorm 设置总结
  • Excel和微软小冰的结合应用
  • 7天用Go从零实现分布式缓存GeeCache(学习)(2)
  • MATLAB双坐标轴的figure图中第2个坐标轴怎么调整大小?
  • 数据结构 ——— 查找链式二叉树中值为X的节点
  • RabbitMQ的DLX(Dead-Letter-Exchange 死信交换机,死信交换器,死信邮箱)(重要)
  • OpenCV通过指针裁剪图像
  • C#绑定窗口句柄,获取后台窗口的图片的实现与分析
  • 聚观早报 | 荣耀Magic7 Pro开售;零跑汽车公布10月销量
  • ElasticSearch从环境搭建到如何使用的全过程
  • 论文解读(21)- RNN,LSTM,GRU
  • QNAP QuMagie相册使用指南
  • percona tpc-c程序压测mysql8.0并绘图
  • 数据库的挂起 提交和事务
  • 学习日记_241110_局部线性嵌入(Locally Linear Embedding, LLE)
  • Hive 查询各类型专利 top10 申请人及专利申请数
  • 20241105编译荣品的Android13并给荣品PRO-RK3566开发板刷机
  • 【网络】传输层——UDP协议