迭代遍历思维
普通人的思维
这不是胡诌,也不是乱扯,普通人的思维与工程师的思维是不一样的,如果你想掌握一门编程语言,就必须像工程师那样去思考,否则,你永远只能是一个门外汉。
lst = [3, 6, 7, 9, 2]
现在,请你用最快的速度说出列表里最大的数,你可以轻而易举的给出正确答案,是9,很好,请你回忆一下,你刚才是怎么找到这个答案的?
你几乎没有去想,你只是看了一眼,就给出了答案,一共只有5个整数,任何人,只要智商正常,只需要看一眼就知道9是这5个数中的最大值,这就是普通人的思维。
迭代遍历思维
现在让我们做一个思想实验,借此引出迭代遍历思维。刚才的列表里只有5个数字,实在太少了。在这个思想实验里,列表里的数据多达1亿个,你可以想象一下,1亿个数据,整个屏幕都装不下了,那么请你从这1亿个数据中找出最大的数值,你应该怎么办?
很显然,你不能再像之前那样一眼就看出答案,那种几乎无需思考的方法只能解决小数据量的问题,当问题的规模扩大到一定程度后,必须使用新的方法,这个方法就是迭代遍历。
准备一张纸,一支笔,一个橡皮擦,在纸上记录这1亿个数中的第一个,从现在开始,假设这个数就是最大的,接下来,将这1亿个数逐个与纸上的数值进行比较,如果比纸上记录的数值大,则修改纸上的数值,这个方法看起来比较笨,但却十分有效,它不会遗漏任何数值,当1亿个数都比较一遍后,这张纸上的数值就是这1亿个数中的最大值,这就是遍历。
这是我想到的方法,相信你经过一段时间的思考,也能想出类似的方法来,下面使用for循环来遍历列表,找出最大值。
lst = [3, 6, 7, 9, 2]max_value = lst[0]for item in lst:if item > max_value:max_value = itemprint(max_value)
不论是for循环,还是while循环,其背后的思维模式都是迭代遍历,重复着去做一件简单的事情,我们不必担心速度,遍历1亿个整数对计算机来说不过是弹指一挥间的事情。
python for循环
Python for循环可以遍历任何可迭代对象,比如列表,字典,集合,字符串等,通过for循环,可以对可迭代对象里的每一个元组执行一组语句,从原理上看,python的for循环与其他编程语言的for循环不太相似,更像是其他面向对象语言中的迭代器方法
下图是for循环的一般形式
- tmpVar 是临时变量
- Iterable 是可迭代对象
- 绿色区域是循环条件,当对可迭代对象的迭代遍历结束时,for循环也就结束
- block是循环体,每一次循环,程序都要进入到这个block内来执行代码
for循环,从形式上可以分为两种
- 使用range函数自由控制循环次数
-
1. range函数
range函数返回一个对象,该对象产生一个整数序列。
range函数有3个参数 start 整数序列开始的位置
- stop 整数序列结束的位置
- step 步长
开始与结束位置所组成的区间[start, stop)是左闭右开的,start默认是0,step默认是1。下面向你展示range函数的使用方法
- range(1, 5) 产生的整数序列是1, 2, 3, 4
- range(0, 4, 2) 产生的整数序列是0,2
- range(5, 0, -1) 产生的整数序列是5, 4, 3, 2, 1
- range(3) 产生的整数序列是0,1,2
2. 使用range函数编写for循环
编写for循环输出从1到10(包括10)的整数
range(0, 11) 产生了整数序列:1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ,结合上一篇教程《迭代遍历思维》的内容来理解这段代码。 range函数产生了一个整数序列,从0到10,for循环的过程就是迭代遍历他们的过程。第一行代码里的i是临时变量,每一次循环,都会改变i的值,i将依次等于1,2 … 9, 10,而每一次将新的值赋值给i以后,都要进入到循环体里执行代码,在这个示例代码中,循环体里只有一行代码print(i),这样就是实现了输出从1到10的功能。for i in range(1, 11):print(i)
下图是这段代码的程序流程图
注意看绿色线串起来的部分,恰好形成了一个闭环,这就是循环。3. 笨拙但朴素,暴力但简单
刚刚学会了for循环,来一道练习题巩固一下吧,请编写程序,输出100以内所有既是2的倍数又是3的倍数的整数。
这个练习题,仍然要使用迭代遍历的思维来解决这个问题,我们不去考虑任何数学上的巧妙算法,仅仅凭借计算机无与伦比的速度,对100以内的每一个整数都进行判断,判断他们是否符合条件
i 依次等于0,1, 2 … 99, 100, 每循环一次,i的值就改变一次,然后进入循环体对i的值进行条件判断,如果符合条件就使用print函数输出i的值。for i in range(101):if i % 2 == 0 and i % 3 == 0:print(i)
是不是很笨拙,你原以为编程是多么高大上的技术,有着常人所不知道秘密,不成想编程竟然就是毫无技术含量的暴力求解,没错,编程就是这样的。但当你习惯这种思维,你会觉得,这是一种很笨拙但是很朴素,很简单的思维,没有任何花里胡哨,没有任何奇技淫巧,就是遍历,从头遍历到尾,每一个数,每一个可能都计算一遍然后得出结果。
那么这么说来,程序员的工作很简单啊!当然不是啦,为了让你更容易接受这个理念和想法,我故意对问题进行了简化,抽象出这么一个你容易理解的描述方式,还存在更复杂的算法,但遍历是所有这些算法的基础,即便是上面这个练习题,仍然可以写出更简单的算法for i in range(0, 101, 3):if i % 2 == 0:print(i)
4. 遍历容器数据
在第3章数据类型中,你已经学习了列表,元组,集合,字典4种容器类型数据,他们都是可迭代对象,关于可迭代对象,我会在进阶教程里专门写教程,就目前而言,你只需要知道这个结论即可,下面的代码向你展示如何判断一个数据是不是可迭代对象
程序输出结果from collections import Iterableprint(isinstance(range(5), Iterable)) # range函数返回值是可迭代对象print(isinstance([1, 2], Iterable)) # 列表print(isinstance((1, 2, 3), Iterable)) # 元组print(isinstance({1, 2, 3}, Iterable)) # 集合print(isinstance({"a": 1, "b": 2}, Iterable)) # 字典
TrueTrueTrueTrueTrue
4.1 遍历列表,元组
元组和列表实在是太相似了,很多操作方法都是相同的,因此我们总是以列表举例子讲解,遍历一个列表,有3种方法
方法1,通过索引遍历
方法2,直接遍历lst = [2, 4, 6, 7]for i in range(len(lst)):print(lst[i])
方法3, 通过enumerate函数遍历lst = [2, 4, 6, 7]for item in lst:print(item)
程序输出结果lst = [2, 4, 6, 7]for index, item in enumerate(lst):print(index, item)
这种遍历方式既能获得数据,也能获得数据的索引,是前面两种方法的融合。0 21 42 63 7
4.2 遍历集合
集合没有索引,因此只能直接遍历set_obj = {1, 2, 3, 3}for item in set_obj:print(item)
4.3 遍历字典
对字典的遍历,有两种常见的方法
方法1, 使用key遍历字典
方法2, 使用字典items()方法dic = {'a': 1,'b': 2}for key in dic:print(key, dic[key])
两种方法最终的输出结果是一样的dic = {'a': 1,'b': 2}for key, value in dic.items():print(key, value)
python 循环中的continue 与 break
continue和break只能在循环中使用,他们以各自的方式让循环更加灵活
1. continue
coninue的中文翻译是继续,在循环体里,continue的作用是跳过当前循环的剩余语句,结束本次循环,继续进行下一轮循环,下面用一个简单的例子来演示continue的功能
lst = [4, 6, 1, 7, 2, 9, 3]for item in lst:if item == 7:continueprint(item)
程序输出结果为
461293
程序输出了列表里所有的数据,除了7以外,我们来看看,7为什么没有输出。循环过程中,item依次等于4, 6, 1, 7, 2, 9, 3,每进行一次循环,就按照顺序将一个新的值赋值给item,然后进入循环体里执行代码语句,当item等于7时,表达式 item == 7 成立,于是进入到if 下面的语句块中执行continue,continue的作用是跳过当前循环的剩余语句,结束本次循环,继续进行下一轮循环,当前循环剩余的语句只有一行,是print(item),continue让程序跳过这行代码,因此没有输出7,下图是程序的流程图
2. continue 与 if
2.1 两种思路比较
如果你足够细心,或许已经发现一个可疑之处,实现同样的功能,是不是用if也可以啊,没错,continue的作用,可以用if来代替,使用if来重写代码
lst = [4, 6, 1, 7, 2, 9, 3]for item in lst:if item != 7:print(item)
既然如此,continue还有存在的必要么?很有必要,continue的核心作用是跳过当前循环剩余的代码,这对于我们思考问题极有帮助。循环过程中,满足某些条件时我们不希望代码继续执行,这个时候用continue就比用if要好很多,假设一个循环体里有两部分
代码块A执行结束后,需要进行一次条件判断以此来决定是否继续执行代码块B,当前的条件是condition, cotinue与if的思路哪个更好,你自己来体会和判断
- continue的思路是,如果当前满足条件condition,代码块B就不执行了,进行下一轮循环,否则执行代码块B
- if的思路是,如果当前条件满足非condition,则执行代码块B,否则不执行,进行下一轮循环
唯一的区别是,使用cotinue时,我们要找跳过代码块B的条件,而使用if 则要找不跳过代码块B的条件,我个人的体会是,找跳过代码块的条件更容易一些,就好比上学时,老师在放学前开玩笑的说:写不完作业,明天就别来上学了,几乎不会有老师说: 写完作业,明天才能来上学,前一个说法更符合我们思考方式。
2.2 continue让代码更容易理解
continue可以更好的控制代码缩进,同时由于其跳过剩余代码,使得代码也更容易理解和阅读,就上一篇教程《for循环》的最后一个练习题来说,寻找列表里的第2大的值,在没有学习continue之前,示例代码是
lst = [4, 6, 1, 7, 2, 9, 3]max_value = lst[0]second_max = lst[0]for item in lst:if item > second_max:if item >= max_value:second_max = max_valuemax_value = itemelse:second_max = itemprint(second_max)
现在,有了continue,可以让代码看起来更加简洁
lst = [4, 6, 1, 7, 2, 9, 3]max_value = lst[0]second_max = lst[0]for item in lst:# 小于等于second_max,就没有必要继续比较了if item <= second_max:continueif item >= max_value:second_max = max_valuemax_value = itemelse:second_max = itemprint(second_max)
当item <= second_max成立时,使用continue跳过剩余的代码,思路更清晰的同时,请注意代码的缩进层次最深为2层,而之前的代码最层为3层,就是这一层之差,就可以让代码更容易阅读,代码的可阅读性十分重要,等你一段代码要写100行时就深有体会。
3. break
满足某个条件时,我们希望能终止循环,这时,你需要使用break语句。不同于continue,break具有很强的破坏力,它的作用是直接停止当前所在的循环。
请编写代码,从下面这个列表里找出一个大于10的数并输出
lst = [1, 4, 5, 10, 2, 11, 15]
显然,你需要对这个列表进行遍历,并判断所遍历到的数据是否大于10,如果大于10,则输出,如果按照这个思路去做,你会发现,你输出的数据不只一个
lst = [1, 4, 5, 10, 2, 11, 15]for item in lst:if item > 10:print(item)
程序最终输出结果
1115
在输出11以后,循环并没有停止,因此后面的15也被输出了,可题目的要求是找出一个大于10的数并输出,而你却输出了两个,为了让循环终止,你需要使用break
lst = [1, 4, 5, 10, 2, 11, 15]for item in lst:if item > 10:print(item)break
程序输出结果
11
当找到一个满足条件的数据时,先使用print输出,然后执行break,终止循环,这样就不会输出15了
嵌套循环
下图是for循环的一般形式
在循环体block中,又是一片全新的天地,你可以写任何你想写的代码,当然包括for循环,应该这样讲,嵌套循环不是什么特殊形式,只是对于初学者来说,嵌套循环不容易被理解,因此,我专门写一篇教程来讲解。
1. 两层嵌套for循环
for i in range(3):for j in range(3):print(i, j)
非常简单的两层嵌套for循环,不执行代码,如果可以直接说出程序的执行结果,那么可以证明你对于for循环的理解很到位,很透彻。如果不能,那么就请认真的听过讲解,程序的输出结果是
0 00 10 21 01 11 22 02 12 2
程序由上而下执行,因此先要执行最外层的循环 for i in range(3) ,循环会进行3轮,先来看第1轮,第一轮循环时,i = 0,进入循环体执行代码,循环体里的代码如下所示
for j in range(3):print(i, j)
这个时候,你就不要管最外层的循环了,把精力放在这个循环上,内层的循环也执行3轮,把上面代码视为一个整体,只有当这个整体执行结束时才能开始外层循环的下一轮。
第1轮
j = 0, 进入循环体执行print(i, j),此时i=0,j=0,输出结果是0 0
第2轮
j = 1, 进入循环体执行print(i, j),此时i=0,j=1,输出结果是0 1
第3轮
j = 2, 进入循环体执行print(i, j),此时i=0,j=2,输出结果是0 2
3轮之后,内层循环结束,此时开始外层循环的第2轮,i=1,老规矩,进入循环体,而循环体里的代码还是
for j in range(3):print(i, j)
得了,内层循环还得3轮,3轮过后,输出
1 01 11 2
随后,开始外层循环第3轮,最终输出
2 02 12 2
2. 3层嵌套循环
无非是比2层多出一层而已,没有什么特殊的,只要准确理解for循环,不管是两层,还是三层,其实都一样。
lst1 = [2, 1, 4, 6, 5]lst2 = [3, 2, 7, 4]lst3 = [4, 5, 2, 3]
从这3个列表里各取出一个数,3个数的和等于10的组合有多少,请写代码输出这些组合,不考虑重复性
lst1 = [2, 1, 4, 6, 5]lst2 = [3, 2, 7, 4]lst3 = [4, 5, 2, 3]for i in lst1:for j in lst2:for k in lst3:if i + j + k == 10:print((i, j, k))
程序输出结果
(2, 3, 5)(2, 4, 4)(1, 7, 2)(1, 4, 5)(4, 3, 3)(4, 2, 4)(4, 4, 2)(6, 2, 2)(5, 3, 2)(5, 2, 3)
你可以看到,但循环达到3层时,代码的缩进更多,更加难以阅读,不仅如此,3层嵌套循环会导致算法复杂度快速变大,导致性能无法满足要求,因此,你要尽量避免写出3层嵌套循环。
3. 嵌套循环的终止
如果循环只有一层,使用break就可以终止循环,但如果循环有两层,事情就变得麻烦
已知有两个列表
lst1 = [2, 7, 4, 6, 5]lst2 = [3, 2, 7, 4]
请编写程序,从两个列表里各取出一个数,使其和为10,这种组合有多个,但要求找到一个即可,你应该已经想到了,一旦找到这种组合,就使用break语句终止循环,请你判断下面的代码能否符合要求。
lst1 = [2, 7, 4, 6, 5]lst2 = [3, 2, 7, 4]for i in lst1:for j in lst2:if i + j == 10:print((i, j))break
程序实际输出结果是
(7, 3)(6, 4)
明明使用了break,为什么还是输出了两个组合呢?回想一下break的作用,break终止当前所在的循环,也就是说当break被执行时,终止的是for j in lst2 这层循环,而外层的for i in lst1 却并没有终止。输出(7, 3)之后,外层的循环继续执行,当i=6时,进入循环体执行for j in lst2 这层循环,最终输出了(6, 4)。
那么,该如何在获得一个组合后终止这两层for循环呢,看下面的示例代码
lst1 = [2, 7, 4, 6, 5]lst2 = [3, 2, 7, 4]stop = Falsefor i in lst1:for j in lst2:if i + j == 10:print((i, j))stop = Truebreakif stop:break
借助变量stop来标识是否已经找到了一组组合,如果找到了,则将stop设置为True,注意看if stop 的缩进和 for j in lst2 是相同的,当内层循环结束后,就会执行这个if判断,满足条件后,再次执行break,这个break所在的循环是for i in lst1 , 最终外层循环也被终止。
python while循环
python中的while语句用于循环执行程序,当给定的判断条件为True时执行循环体,循环体里是一段需要重复执行的代码。每一次执行完循环体都要重新对判断条件进行计算,只有当判断条件为False的时候才会终止循环, 此外,也可以使用break语句终止循环。
1. python while循环的一般形式
while 表达式:代码块
- 只要表达式的结果是True,就会执行循环体里的代码
- 当循环体里的代码执行结束后,会再次进行条件判断,如果表达式的结果是True,会继续执行循环体里的代码
思考题,下面的代码,会永远执行下去么?
while True:print('while')
上面的代码会一直执行下去,形成了死循环,除非你有意实现一个死循环,否则,这样的死循环会导致致命的问题
2. for循环与while循环对比
for循环,本质上是迭代,在循环之前,你基本上是知道要循环多少次的,但是while循环则不然,有时候你知道要循环多少次,但有些while循环,你根本不知道要循环多少次。
for循环举例子
lst = [1,2,3]for item in lst:print(item)
这段代码,你看了,就知道会循环3次,同样是循环遍历lst,用while循环实现一下
lst = [1,2,3]index = 0while index < len(lst):print(lst[index])index += 1
看到代码,你也能判断循环会执行多少次,但下面的代码,会执行多少次,你就无法知晓了
while True:input_str = input("请输入一个正整数,想退出程序请输入 quit:")if input_str == "quit":breaknumber = int(input_str)if number % 2 == 0:print("你输入的是一个偶数")else:print("你输入的是一个奇数")
这个循环要执行多少次,你事先是无法知晓的,因为什么时候退出,取决于用户的输入,请思考一下,如果用for循环来实现上面的这个功能,应该怎么做?
尽管在python里,通过技术手段,可以实现for循环的无限循环,但我们不这样做,那些需要无限循环的代码,使用while True 这种形式就可以了。
