0%

python 学习日记(2) 推导式、迭代器、生成器

  python 的推导式是一种独特的数据处理方式,可以从一个数据序列构建另一个数据序列的结构体。Python 推导式是一种强大且简洁的语法,适用于生成列表、字典、集合和生成器。在使用推导式时,需要注意可读性,尽量保持表达式干净简洁,以免影响代码的可读性和可维护性。Python 支持各种数据结构的推导式:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式
  • 元组(tuple)推导式

列表推导式

  列表推导式的格式如下

1
2
3
4
5
6
7
[表达式 for 变量 in 列表] 
[out_exp_res for out_exp in input_list]

或者

[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]

  其中,各个值含义如下:

  • out_exp_res:列表生成元素表达式,可以是有返回值的函数。
  • for out_exp in input_list:迭代 input_list 将 out_exp 传入到 out_exp_res 表达式中。
  • if condition:条件语句,可以过滤列表中不符合条件的值。

  如下代码可以筛选出原序列中可以被 3 整除的数

1
2
3
raw_list = [5,6,7,8,9,0,1,2,3,4,5]
filter_list = [l for l in raw_list if l%3 == 0]
print(filter_list)

  如下代码可以筛选出原序列中长度大于 3 的字符串

1
2
3
raw_list = ['hcw','your','sn','asdasf']
filter_list = [l for l in raw_list if len(l)>3]
print(filter_list)

字典推导式

  字典推导基本格式:

1
2
3
4
5
{ key_expr: value_expr for value in collection }



{ key_expr: value_expr for value in collection if condition }

  使用示例如下:

1
2
3
4
listdemo = ['Google','Runoob', 'Taobao']
# 将列表中各字符串值为键,各字符串的长度为值,组成键值对
new_dict = {key:len(key) for key in listdemo}
print(new_dict)

集合推导式

  集合推导式基本形式如下:

1
2
3
{ expression for item in Sequence }

{ expression for item in Sequence if conditional }

  比如可以使用如下代码来实现一个 x^2 的集合

1
2
setnew = {i**2 for i in range(1,6)}
print(setnew)

元组推导式

  元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。其基本格式如下:

1
2
3
(expression for item in Sequence )

(expression for item in Sequence if conditional )

  元组推导式和列表推导式的用法也完全相同,只是元组推导式是用 () 圆括号将各部分括起来,而列表推导式用的是中括号 [],另外元组推导式返回的结果是一个生成器对象。最后使用 tuple 函数,可以直接将生成器对象转换成元组。

迭代器

  迭代是 Python 最强大的功能之一,是访问集合元素的一种方式。
  迭代器是一个可以记住遍历的位置的对象。
  迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
  迭代器有两个基本的方法:iter() 和 next()。
  字符串,列表或元组对象都可用于创建迭代器:

1
2
3
4
5
6
testlist = [1,2,3,4]
it = iter(testlist)
print(next(it))
print(next(it))
print(next(it))
print(next(it))

  运行如上程序,会输出 1、2、3、4。迭代器对象还可以使用常规for语句进行遍历,如下代码也会输出 1 2 3 4 。

1
2
3
4
testlist = [1,2,3,4]
it = iter(testlist)
for i in it:
print(i)

  把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__() 。Python 中类的构造函数为 __init()__,它会在对象初始化的时候执行。
  __iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 next() 方法并通过 StopIteration 异常标识迭代的完成。
  __next__() 方法会返回下一个迭代器对象。

  如下代码创建一个返回数字的迭代器,初始值为 1,逐步递增 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Mynumbers:
def __iter__(self):
self.a = 1
return self

def __next__(self):
x = self.a
self.a += 1
return x
myclass = Mynumbers()
myiter = iter(myclass)

print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

  StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Mynumbers:
def __iter__(self):
self.a = 1
return self

def __next__(self):
if self.a < 20:
x = self.a
self.a += 1
return x
else:
raise StopIteration
myclass = Mynumbers()
myiter = iter(myclass)

for x in myiter:
print(x)

生成器

  在 Python 中,使用了 yield 的函数被称为生成器(generator)。

  yield 是一个关键字,用于定义生成器函数,生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。

  跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。

  当在生成器函数中使用 yield 语句时,函数的执行将会暂停,并将 yield 后面的表达式作为当前迭代的值返回。

  然后,每次调用生成器的 next() 方法或使用 for 循环进行迭代时,函数会从上次暂停的地方继续执行,直到再次遇到 yield 语句。这样,生成器函数可以逐步产生值,而不需要一次性计算并返回所有结果。

  调用一个生成器函数,返回的是一个迭代器对象。

  下面是一个简单的示例,展示了生成器函数的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def countdown(n):
while n > 0:
yield n
n -= 1


# 创建生成器对象
generator = countdown(5)

# 通过迭代生成器获取值
print(next(generator)) # 输出: 5
print(next(generator)) # 输出: 4
print(next(generator)) # 输出: 3

# 使用 for 循环迭代生成器
for value in generator:
print(value) # 输出: 2 1

  以上实例中,countdown 函数是一个生成器函数。它使用 yield 语句逐步产生从 n 到 1 的倒数数字。在每次调用 yield 语句时,函数会返回当前的倒数值,并在下一次调用时从上次暂停的地方继续执行。
  通过创建生成器对象并使用 next() 函数或 for 循环迭代生成器,我们可以逐步获取生成器函数产生的值。在这个例子中,我们首先使用 next() 函数获取前三个倒数值,然后通过 for 循环获取剩下的两个倒数值。
  生成器函数的优势是它们可以按需生成值,避免一次性生成大量数据并占用大量内存。此外,生成器还可以与其他迭代工具(如for循环)无缝配合使用,提供简洁和高效的迭代方式。
  如下代码石使用 yield 实现斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def fibonacci(n):
a,b,counter = 0, 1, 0
while True:
if counter > n:
return
yield a
a , b = b, a + b
counter += 1

# 创建生成器对象
generator = fibonacci(15)

# 使用 for 循环迭代生成器
for value in generator:
print(value)

做题

  力扣 2105.给植物浇水

  Alice 和 Bob 打算给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。

  每一株植物都需要浇特定量的水。Alice 和 Bob 每人有一个水罐,最初是满的 。他们按下面描述的方式完成浇水:

  • Alice 按 从左到右 的顺序给植物浇水,从植物 0 开始。Bob 按 从右到左 的顺序给植物浇水,从植物 n - 1 开始。他们 同时 给植物浇水。
  • 无论需要多少水,为每株植物浇水所需的时间都是相同的。
  • 如果 Alice/Bob 水罐中的水足以 完全 灌溉植物,他们 必须 给植物浇水。否则,他们 首先(立即)重新装满罐子,然后给植物浇水。
  • 如果 Alice 和 Bob 到达同一株植物,那么当前水罐中水 更多 的人会给这株植物浇水。如果他俩水量相同,那么 Alice 会给这株植物浇水。

  给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有两个整数 capacityA 和 capacityB 分别表示 Alice 和 Bob 水罐的容量。返回两人浇灌所有植物过程中重新灌满水罐的 次数 。

答案

  想法很简单,使用暴力模拟,使用两个 index 去从左到右和从右到左遍历数组,写下如下代码,顺利通过,并且来到了时间复杂度为 O(n) 的最优解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution:
def minimumRefill(self, plants: List[int], capacityA: int, capacityB: int) -> int:
left_index, right_index, add_times= 0, len(plants) - 1, 0
a_max, b_max = capacityA, capacityB
while True:
if left_index == right_index:
if capacityA >= capacityB:
if capacityA < plants[left_index]:
add_times += 1
return add_times
else:
return add_times
else:
if capacityB < plants[right_index]:
add_times += 1
return add_times
else:
return add_times
elif left_index > right_index:
return add_times

if capacityA >= plants[left_index]:
capacityA -= plants[left_index]
left_index += 1
elif capacityA < plants[left_index]:
capacityA = a_max
capacityA -= plants[left_index]
add_times += 1
left_index += 1

if capacityB >= plants[right_index]:
capacityB -= plants[right_index]
right_index -= 1
elif capacityB < plants[right_index]:
capacityB = b_max
capacityB -= plants[right_index]
add_times += 1
right_index -= 1