本帖最后由 wdc63 于 2013-9-11 01:15 编辑
第四课 循环 & 图灵完备系统 第三课传送门 NCF显示不好,要获得更好的阅读体验,请访问我的博客:www.alwayswdc.com,但请在NCF进行回复讨论 由于参加导师教师节聚会,回来更新有点晚。迟到的祝下全天下的教师节日快乐!!
一 循环
我们学了前两种程序执行结构,现在来看第三种:循环。循环是一段 在程序中只出现一次,但可能会连续运行多次的代码或代码组。正是由于"循环"的存在,计算 机的快速计算能力才真正有了用武之地,否则算法的每一行都需要人工敲进去,那和在纸上进行 计算有何异? 通常来讲,程序语言中的循环有两种结构形式:当型、直到型、遍历 型循环。Python中没有直到型循环(一般为 Do loop Until结构,这里 就不详讲,请各位自查资料),Python中的当型循环和遍历型循环分别对 应为w.hile语句和For语句。
1.1 当型循环
当型循环是在执行循环体前首先进行判断,当条件满足时进入循环, 否则结束循环,当型循环也叫"前测试型"循环。在Python当中, 进行当型循环的语句为while语句。其语法形式如下: 注意while为小写,另外while语句也可以带有else字句,当while内的条件不满足时且循环体内没有break语句,会执行else字句内的内容,但这种方法不太常用。
while 判断表达式: 语句体 1(不包含break句) else: 语句体 2
|
while语句的执行流程图如下: 注意使用while循环时要思考好判断条件,如果判断条件如果恒为真而在执行语句体 内又没有满足某条件跳出的语句时会形成一个死循环,也就是程序将无限循环下去,来看一个使 用while循环的简单例子,在屏幕上打印出 我们熟悉的斐波那契数列。 我们知道斐波那契数列是一个从数列第三项开始,每一项都等于前两 项之和的数列。即A[n+2]=A[n+1]+A[n],这里我们顺便复习 一下列表的知识,使用列表来放置这一系列数列,使用list.append()方法来向数组内添加一个 元素。代码如下:(1_shulie.py):
An_2 = 1 An_1 = 1 shulie = [An_2,An_1] while 1: An = An_2 + An_1 An_2 = An_1 An_1 = An shulie.append(An) if len(shulie) > 100: break print (shulie)
|
>>> ================================ RESTART================================ >>> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229,832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817,39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733,1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025,20365011074, 32951280099, 53316291173, 86267571272, 139583862445,225851433717, 365435296162, 591286729879, 956722026041, 1548008755920,2504730781961, 4052739537881, 6557470319842, 10610209857723,17167680177565, 27777890035288, 44945570212853, 72723460248141,117669030460994, 190392490709135, 308061521170129, 498454011879264,806515533049393, 1304969544928657, 2111485077978050, 3416454622906707,5527939700884757, 8944394323791464, 14472334024676221,23416728348467685, 37889062373143906, 61305790721611591,99194853094755497, 160500643816367088, 259695496911122585,420196140727489673, 679891637638612258, 1100087778366101931,1779979416004714189, 2880067194370816120, 4660046610375530309,7540113804746346429, 12200160415121876738, 19740274219868223167,31940434634990099905, 51680708854858323072, 83621143489848422977,135301852344706746049, 218922995834555169026, 354224848179261915075,573147844013817084101]
|
居然第100个斐波那契数列如此大。这里我用了一个恒为真的条件进行循环,并 在循环执行语句内进行判断是否满足某一条件,斐波那契数列达到100个数的长度时,即使用break语句跳出循环,更为简洁的算法应 该是直接将这一条件置于while之后,即while len(shulie) > 100。 提到列表,我想有必要再讲一下相关知识,毕竟第二课中只是简略的 讲解了一下。前面讲了Python中有四种来对数据集合的方式 Tuple(元组)、List[列表] Dictionary{字典}、set{集合}。今天详讲前两种,因为我认为对于我们后面要频繁用到Rhino.python编程的建筑学子来说,这两种数据集合最为常用。 • Tuple(元组) Tuple(元组)是一个有序且一旦声明就 不可变的数据集合,其声明方式使用小括号(数据1,数据2,...)。元组支持切片、索引等操作,这点跟List[列表]相似,使用内置函数tuple() 可将一个序列(字符串、列表、集合)转换为元组。元组由于不可修 改,因此不带任何专属函数。在Rhino.Python中,可以使用元组表示一个 点,或点集。(2_tuple.py)
##元组运行建立一个元素或者空元 组,当建立一个只有一个元素的元组时,在该元素之后加上逗号,',' a = () b = (1,) print (a,b) ##元组内可以包含任意的其他类型 变量,包括数字、字符串、元组、列表等 c = ('good',1,'school',4,6,22.5, ['abc',123], ('asd', ['2',3,4])) ##切片索引等操作,,详见第二课 内容 print (c [0],c[- 1],c [6]) print (c [1:3]) print (c[- 3:- 1]) ##使用tuple() 函数将一个序列转换成元组 d = tuple('ducument') e = tuple(c[-1]) f = tuple(c[-2]) print (d,e,f)
|
>>> ================================ RESTART================================ >>> () (1,) good ('asd', ['2', 3, 4]) ['abc', 123] (1, 'school') (22.5, ['abc', 123]) ('d', 'u', 'c', 'u', 'm', 'e', 'n', 't') ('asd', ['2', 3, 4]) ('abc', 123)
|
元组虽然定义的是元组内的元素不可改变,但这句话也不是绝对的, 如果元组内是一个变量,那个这个变量发生改变,元组也相应发生改变,如果元组内包含列表, 那么我们也可以更改列表。因此这句话这样描述更为准确:元组中指向的各个成员的地址是不可 改变的。更改元组中变量的值,虽然指向这个变量的地址没有发生改变,但其真实值发生了变化 。
>>> a = [3,2,1] >>> b = (a,4,5,6,7) >>> a.sort() >>> a [1, 2, 3] >>> b ([1, 2, 3], 4, 5, 6, 7)
|
• List[列表] A 列表定义及基本操作 Python中的列表是一个有序的可变数据集 合,熟悉C或VB的同学可以用array来理解list [列表],和元组比较类似,列表中可以包含任意其他类型变量,使用中括号 [数据1,数据2,...]来进行定义。在Rhino.Python中,也可以使用列表表示一个点,或点集。 首先来看基本的列表建立,赋值,及索引,切片等操作。 (3_list.py)
##初始建立一个列表有多种方法 ##直接赋值 a = [1,2,3,4,5] ##对字符串使用spit() 函数 b = 'always'.split() ##使用range() 函数建立一个列表,即一个从起 始整数到末尾整数(不包含该数)的间隔1等差数列 c = [range (10)] print (a,b,c) ##列表的索引切片与元组类似 print (a [0],a[- 1],c [4]) print (b [1:3]) print (c[- 3:- 1]) ##对列表中的元素可以重新赋值 a [0],a [1] =99,100 b [3] = ['big','arc',23.5] print (a,b) ##列表与列表可以进行相加得到一 个新列表(注意这里的相加不是布尔运算,仅有set集合才具有布尔运算功能) print (a,b) print (a +b)
|
>>> ================================ RESTART================================ >>> [1, 2, 3, 4, 5] ['a', 'l', 'w', 'a', 'y', 's'] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1 5 4 ['l', 'w'] [7, 8] [99, 100, 3, 4, 5] ['a', 'l', 'w', ['big', 'arc', 23.5], 'y', 's'] [99, 100, 3, 4, 5] ['a', 'l', 'w', ['big', 'arc', 23.5], 'y', 's'] [99, 100, 3, 4, 5, 'a', 'l', 'w', ['big', 'arc', 23.5], 'y', 's']
|
B 列表的方法 ① 向列表中新增元素 向一个列表中新增元素有四种方式,列表相加(前面已讲)、 append()、extend()、insert()后三种都属于列表的方法。(4_list.method.py)
a = list(range(5)) ##列表相加 a = a + ['a','b','c'] print (a) ##append ()方法可以向列表内尾部添加一个 元素,可以是单个变量、元组、列表等 a.append (['d','e','f']) print (a) ##extend ()方法接收一个列表参数,将新列 表中的每个元素添加到原列表中,注意如果是该列表内含有列表或元组,那么子列表或元组不会 被分开,如果是一个字符串,字符串会被分开成每个字符。 a = list(range(5)) a.extend ('hello') a.extend (['a','b','c', ['d','e','f']]) ##insert ()方法在列表指定位置插入一个元 素。注意是插入到该位置原有元素的前面。 a = list(range(5)) b = list(range(6,10)) a.insert (1,b) a.insert(- 2,10)
|
>>> ================================ RESTART================================ >>> [0, 1, 2, 3, 4, 'a', 'b', 'c'] [0, 1, 2, 3, 4, 'a', 'b', 'c', ['d', 'e', 'f']] [0, 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o'] [0, 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o', 'a', 'b', 'c', ['d', 'e', 'f']] [0, [6, 7, 8, 9], 1, 2, 3, 4] [0, [6, 7, 8, 9], 1, 2, 10, 3, 4]
|
② 列表删除元素 列表中删除元素有三种方式,del、remove()、pop()。后两种属于列表方法。
##列表中删除元素### a = list(range(10)) ##del函数按索引或切片进行删除,类 似于GH中的Cull Index。 del a [1:3] print (a) ##remove ()方法按元素的值进行删除一个元 素,若不存在该元素,会报错 a.remove (4) print (a) ##pop() 方法类似于del,通过索引(不包括切片)删除 列表中元素,但它返回删除的这个值,不指定参数返回最后一个。 a = list(range(10)) b = a.pop() c = a.pop (5) print (b,c)
|
>>> ================================ RESTART================================ >>> [0, 3, 4, 5, 6, 7, 8, 9] [0, 3, 5, 6, 7, 8, 9] 9 5
|
③ 乱序、排序与倒序 sort()方法对列表进行排序, reverse()方法对列表进行倒序。乱序需 要用到random模块中的sample(seq,k)方法,seq为一个序列,k为序列中选取的长度值,这个方法可以 保证不重复。
##列表排序与倒序### import random ##列表乱序 a = list(range(10)) a = random.sample(a,len (a)) print (a) ##列表排序,一般情况下字符串和 数字不能混排序 a.sort() print (a) ##列表倒序 a.reverse() print (a)
|
>>> ================================ RESTART================================ >>> [6, 7, 4, 2, 8, 9, 1, 3, 0, 5] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
|
发散思维: 1如何对字符串和数字同时存在的列表进 行排序,并要求数字在字符串前面 2 如何通过字符串长度对列表进行排序 尽量用最简便的方法,不要用太猥琐的方法把列表拆开再装进去,下 课揭晓答案
④ 列表检索 len()函数检索列表长度,判断某个元素 是否在列表或不在用in和not in操作符,统计某个元素在列表中出现的次数用count()方法,检查某个元素在列表中出 现的位置用index()方法。
##列表检索### a = ['a','c','d','a','b','3'] ##len() 检索列表长度 print (len (a)) ##断某个元素是否在列表或不在用 in和not in操作符 print ('a' in a ) print ('a' not in a ) ##统计某个元素在列表中出现的次 数用count() 方法 print (a.count ('a')) ##检查某个元素在列表中首次出现 的位置用index() 方法 print (a.index ('a')) print (a.index ('a',2,5)) ##指定切片内的某个元素出现位置
|
>>> ================================ RESTART================================ >>> >>> 6 True False 2 0 3
|
1.2 遍历型循环For 遍历型循环会对一个数据集合进行遍历,当遍历到最后一个元素时, 循环结束,它实际上属于一种迭代。Python中的for循环会访问一个可迭代对象(包括数据集合、字符串、迭代器等)的 所有元素,直到访问最后一个元素之后结束,其语法形式如下:
语句体2 每次循环,i迭代变量被赋值为当前的访问对象,可提供给循环执行语句使用。其 结构流程图如下: 1.2.1 可迭代对象有哪些 我们来看看有哪些对象属于Python中的可迭代对象。 • 字符串 使用字符串作为for语句中的可迭代对象时,迭代变量会访问每一个字符(赋值为长度为 1的字符串),但这一般不太常用,要检测某个字符是否在某个字符串 内,一般会用到in测试法(见上文的数组知识)。
>>> for i in ('ChongQing'): print (i) C h o n g Q i n g >>>
|
• 列表和元组 for循环遍历一个列表或数组是最为常用 的方法,如果单纯需要for进行一个计数器循环,一般直接使用 range()函数来创造一个等差数列式的数 字列表。range()语法为range(初始值,终止值,步长 ),只支持整数类型,生成的列表包含初 始值但不包含终止值。Python2.x中还有 xrange()方法,与range()不同之处在于xrange()不会生成一个完整的列表,只有 需要时才生成该对象,更为节约空间。(另外字典与集合也是可以做为可迭代对象,对于字典, 会遍历其key值,对于集合则同时访问所有对象, 访问次数为集合的长度。这两种情况一般不用,请自行尝试。) 例如在python2.7中:
>>> a = range(100) >>> b = xrange(100) >>> a [0, 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, 39, 40, 41, 42,43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99] >>> b xrange (100)
|
使用列表或元组进行循环迭代一般有两种方式(5_forloop1.py)。 ① 通过序列对象进行迭 代
import random first_name = '赵 钱 孙 李 周 吴 郑 王' last_name = '梅 阳 林 妮 博 宝 冰 波 贝' first_name = random.sample (first_name.split (),len (first_name.split())) last_name = random.sample (last_name.split (),len (last_name.split())) for i in first_name: for j in last_name: print(i +j,' ',end = '')
|
>>> ================================ RESTART================================ >>> >>> 吴妮 吴林 吴阳 吴博 吴波 吴宝 吴梅 吴贝 吴冰 周妮 周林 周阳 周博 周波 周宝 周梅 周贝 周冰 钱妮 钱林 钱阳 钱博 钱波 钱宝 钱梅 钱贝 钱冰 赵妮 赵林 赵阳 赵博 赵波 赵宝 赵梅 赵贝 赵冰 李妮 李林 李阳 李博 李波 李宝 李梅 李贝 李冰 王妮 王林 王阳 王博 王波 王宝 王梅 王贝 王冰 郑妮 郑林 郑阳 郑博 郑波 郑宝 郑梅 郑贝 郑冰 孙妮 孙林 孙阳 孙博 孙波 孙宝 孙梅 孙贝 孙冰
|
② 通过序列索引进行迭 代
for i in range(len(first_name)): for j in range(len(last_name)): print (first_name [i]+ last_name[j],end = '')
|
• 迭代器 迭代器是一种对象内涵元素,并支持next()方法进行逐一迭代,iter()和 enumerate()函数可将一个数据集合变成迭代器,其中enumerate()每次迭代对象中包含了迭代次数和元素两个对象。当迭代完成时,会 返回StopIteration错误。 for循环在本质上其实就是调用iter ()迭代器。 使用iter()作为for循环中的可迭代对象相当于一个数组,意义不大,一般会使用 enumerate()函数,相当于同时使用索引和序列对象进行迭代。
>>> name_list = '赵 钱 孙 李 周 吴 郑 王' >>> import random >>> name = random.sample (name_list.split (),len (name_list.split())) >>> for i,j in enumerate(name): print ('%d %s大川 ' %(i +1,j)) 1 郑大川 2 赵大川 3 孙大川 4 周大川 5 吴大川 6 钱大川 7 王大川 8 李大川 >>>
|
• 其他可迭代对象 sorted()、reverse()、zip()等函数也可以作为for循环中的可迭代对象。 sorted()和reverse()相当于列表的sort()方法,不同之处在于它们是建立一 个新的列表对象,就不详讲。zip()也类似于一个迭代器。它将若干个 数据集合的第n个元素放在一起,打包成一个元组。有 点类似于Grasshopper中的二维矩阵变换。使用 *可以解包。
>>> a = [1,3,5,7] >>> b = [2,4,6,8] >>> c = zip(a,b) >>> list(c) [(1, 2), (3, 4), (5, 6), (7, 8)] >>> list(zip(*c)) [(1, 3, 5, 7), (2, 4, 6, 8)]
|
>>> for i,j in c: print(i,j) 1 2 3 4 5 6 7 8
|
2 循环案例 前面的知识学习了想必对于for循环的使用已经差不多掌握了,到现在有些接触其他语言的同学可能 会问,循环和迭代在其他语言中常常作为两个独立的部分来讲解,由于C语言等语言中会是for i=1,i<10,i++{}这样的形式,是 一种当型循环,而又采用了一种foreach in来实现迭代。但是在 Python当中,你可以从理论上去了解两者 的区别,但大可不必把两者分得太开,实际上遍历循环就是一种迭代。如果以条件控制循环时, 就用while,如果以一个计数器作为控制循环 条件时就用for,Python确实很简便。 现在我们来看一个图形化使用循环的例子。呵呵,终于接触到一次图形化编程了,不过我们目前还不在 Rhino.Python上进行,而是基于Python自带的一个图形化脚本,想早点看到图形算法的 同学请勿急,很多从未学习编程的同学需要掌握最基础的知识,本课后面会有图形生成的例子, 但要到真正讲解Rhino.Yython还要等基础部分全部讲完之后。 这里我们要用到一个绘图库,叫turtle,turtle字面意思是乌龟,我在第一课提到的我学的第一门编程语言 LOGO的图表便是一只小乌龟,没错,turtle就是从LOGO语言移植过来的,语法简单,非常适合儿童学习编程,但又很强大, 有非常齐全的二维绘图方式和各种事件处理能力,网上有不少使用turtle写的游戏,各位可以去搜索下。
2.1模块导入 turtle虽然是Python安装自带库,但不属于核心库,在 使用之前我们需要导入这个库。这里我们首先来学习我们前面已经用到过许多次的方法 ---导入模块。 模块在Python中也就是一部分代码的集合,可以是变量定义、函数或类,它被放在 一个.py文件中,关于模块我们在后面接触了函数和类之后还会详讲。在 Python中可以使用import方法导入自带模块、第三方模块或自定义模块,import方法有两种基本形式:
as别名是可选的,也就是将模块名用自定 义名来表示,例如使用Rhino.Python语法一般使用 importrhinoscriptsyntax as rs。只要导入了一个模块,就可以引 用其任何成员函数、类、全局变量或属性。但是要注意引用时需要带上名字或别名。 第二种方式为
from 模块import 子对象 1,子对象2.. from 模块import *
|
与第一种方法的区别是,子对象的名字直接被导入到了本地的命名空 间内,可以直接使用,不再需要加上模块名的限定,如果使用一个*号的话,这该模块下的所有子对象都导 入。
导入模块的方法比较简单,但是需要有以下两点注意:
① 要注意导入模块后的 名字问题,因为在Python中所有对象的名字是独一无二的, 所以如果导入了两个模块都使用:from 模块import *的方法,而两个模块下恰好有命名一样的子对象,后 一个对象会覆盖前一个对象。因此要尽量避免使用from 模块import *的方法。
② Python导入模块机制与C不一样,在声明时变会初始化,占用程序内存,一般按需导入,例如 在Rhino.Python只需要导入颜色相关的类,会使用import System.Drawing.Color 而不是导入整个 System。
2.2 Koch分形雪花 想必这个论坛上有许多文章介绍过这个图形了,用Grasshopper自带运算器的暴力循环或者用vb、C#等方法,现在我们来看看如何不在Rhino环境下来实现。 Koch是一个经典的L-system图形,以一个正三角形作为基本 型进行变化得到。每次迭代按照如下规则进行,每条边分成三等分,其中中间的一段被替换成一 个没有底的三角形,变化之后,三条边变成四条边,每条边的长度依然不变。
turtle绘图是一个给定规则然后连续绘图 的过程,因此我们需要再小海龟绘图之前定义好其所有路径。 这里我们用到turtle两个函数即可,forward()前进和left()左转,初始三角形为小乌龟前进一个初始长度,左转-120°,然后走一个初始长度,再左转- 120°,然后再走一个初始长度,我们可以定义初始状态如下: [初始长度 , -120°, 初始长度 , -120°, 初始长度] 这样便定义了初始三角形。 每进行一次迭代,每个初始长度都被替换为以下动作: [长度/3 , 60°, 长度/3 , -120°, 长度/3 , 60°, 长度/3 ] 我们使用一个列表来记录所有的小乌龟运动路径,然后再让小乌龟读 取列表中的,每个值按照路径移动即可。 整个代码如下(6_koch.py):
#-*- coding:utf-8 -*- __author__ = 'Wang Dachuan @ChongQing Univercity' __copyright__ = '<共享,非商业,署名>' from _tkinter import _flatten import turtle ##定义基本变量,初始状态和迭代 替换 l = 'line' length = 500 angle = 60.0 base = [l,- angle*2,l,- angle*2,l] xi = [l,angle,l,- angle*2,l,angle,l] ##迭代过程 for i in range(5): ##替换过程 for j in range(len(base)): if l == base [j]: base[j] = xi length = length/3 ##_flatten为将多维数组拍平函数 base = list(_flatten (base)) ##turtle绘图 turtle.up() turtle.goto(- 250,160) turtle.down() turtle.speed (10) for i in base: if i == l: turtle.forward(length) else: turtle.left(i) turtle.done()
|
二 图灵完备系统 各位,这里我要告诉你们,恭喜你们,关于一门编程语言需要学习的 最基础知识你已经学完了!有同学会问,不是才刚刚开始么?是的,对于编程来说这才是刚刚起 步,不过就像我们学习经典力学的三大基本定理,编程世界中的基本定理你已经全部学完。 当代计算机学家和数学家已经证明,任何一门拥有变量赋值和基本运 算、条件判断、循环和虚拟机的计算机语言都是一个图灵完备系统。何谓图灵完备系统?图灵完 备系统是一个资源无限系统,它可以解决任何可计算问题。 我们前面学习了变量、变量赋值与运算、条件判断和循环,也就是说 如果你的计算机内存足够大,你可以用这些知识做任何现在人家用计算机做到的事情,从开发一 个Rhino一样的绘图程序到开发 Windows一样的操作系统,使用这些就行 了。你听说过的这些当代高级语言C++、VB、JAVA、PHP都是图灵完备语言,计算机软硬件架构是基于图灵完备思想建立起来 的,因此这些语言在底层原理上都是类似的。有些同学疑虑学习Python怕人家使用C++的同学能够实现的东西你不能够实现 ,从非常严格的理论角度来说,C++能实现的东西没有Python不能够实现的。 但是不要因此而得意洋洋,对于学习编程来讲,你还仅仅开始。你只 学习了公理,但相信我的读者没有一个是这样的天才:经典力学三大公理可以独立推导出 麦克斯韦方程,热力学方程、混沌理论。你若想较好的掌握这个工具,你在后面需 要花时间学习构建函数以对代码重用,学习各种抽象方法,将现实生活中的对象用语言表达, 学习各种必须掌握的经典算法,学习高手代码的写法,学习各 种图形学和优化知识以辅助我们的工作和学习。 朋友们加油,我会持续更新!
练习:1 之前提到的发散思维:如何对字符串和数字同时存在的列表进 行排序,并要求数字在字符串前面; 如何通过字符串长度对列表进行排序请尽量独立解决2 用循环完成其他的L-system例子
完成的作业有疑问的可以发到i@alwayswdc.com,我会尽量帮助您。
|