第四课 循环 & 图灵完备系统

一 循环

我们学了前两种程序执行结构,现在来看第三种:循环。循环是一段在程序中只出现一次,但可能会连续运行多次的代码或代码组。正是由于“循环”的存在,计算机的快速计算能力才真正有了用武之地,否则算法的每一行都需要人工敲进去,那和在纸上进行计算有何异?

通常来讲,程序语言中的循环有两种结构形式:当型、直到型、遍历型循环。Python中没有直到型循环(一般为Do  loop  Until结构,这里就不详讲,请各位自查资料),Python中的当型循环和遍历型循环分别对应为w.hile语句和For语句。

1.1 当型循环

当型循环是在执行循环体前首先进行判断,当条件满足时进入循环,否则结束循环,当型循环也叫“前测试型”循环。在Python当中, 进行当型循环的语句为while语句。其语法形式如下:

while 判断表达式:

语句体

注意while为小写,另外while语句也可以带有else字句,当while内的条件不满足时且循环体内没有break语句,会执行else字句内的内容,但这种方法不太常用。

while 判断表达式:

语句体1(不包含break句)

else:

语句体2

while语句的执行流程图如下:

image001

注意使用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

for i in 可迭代对象:

循环语句体1

每次循环,i迭代变量被赋值为当前的访问对象,可提供给循环执行语句使用。其结构流程图如下:

image002

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

1.2.2 for循环案例

前面的知识学习了想必对于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写的游戏,各位可以去搜索下。image003

关于这个库的详细文档请参见。http://docs.python.org/3/library/turtle.html?highlight=turtle#turtle,关于turtle的使用方法我就不详讲,请在开始这个例子前自学一下其中功能。

1.2.2.1模块导入

turtle虽然是Python安装自带库,但不属于核心库,在使用之前我们需要导入这个库。这里我们首先来学习我们前面已经用到过许多次的方法—导入模块。

模块在Python中也就是一部分代码的集合,可以是变量定义、函数或类,它被放在一个.py文件中,关于模块我们在后面接触了函数和类之后还会详讲。在Python中可以使用import方法导入自带模块、第三方模块或自定义模块,import方法有两种基本形式:

import 模块 [as 别名]

as别名是可选的,也就是将模块名用自定义名来表示,例如使用Rhino.Python语法一般使用import rhinoscriptsyntax as rs。只要导入了一个模块,就可以引用其任何成员函数、类、全局变量或属性。但是要注意引用时需要带上名字或别名。

第二种方式为

from 模块import 子对象1,子对象2..

from 模块import *

与第一种方法的区别是,子对象的名字直接被导入到了本地的命名空间内,可以直接使用,不再需要加上模块名的限定,如果使用一个*号的话,这该模块下的所有子对象都导入。

导入模块的方法比较简单,但是需要有以下两点注意:

① 要注意导入模块后的名字问题,因为在Python中所有对象的名字是独一无二的,所以如果导入了两个模块都使用:from 模块import *的方法,而两个模块下恰好有命名一样的子对象,后一个对象会覆盖前一个对象。因此要尽量避免使用from 模块import *的方法。

② Python导入模块机制与C不一样,在声明时变会初始化,占用程序内存,一般按需导入,例如在Rhino.Python只需要导入颜色相关的类,会使用import System.Drawing.Color 而不是导入整个System。

1.2.2.2 Koch分形雪花

image004想必这个论坛上有许多文章介绍过这个图形了,用Grasshopper自带运算器的暴力循环或者用vb、C#等方法,现在我们来看看如何不在Rhino环境下来实现。

Koch是一个经典的L-system图形,以一个正三角形作为基本型进行变化得到。每次迭代按照如下规则进行,每条边分成三等分,其中中间的一段被替换成一个没有底的三角形,变化之后,三条边变成四条边,每条边的长度依然不变。

image005

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()

image006

二 图灵完备系统

各位,这里我要告诉你们,恭喜你们,关于一门编程语言需要学习的最基础知识你已经学完了!有同学会问,不是才刚刚开始么?是的,对于编程来说这才是刚刚起步,不过就像我们学习经典力学的三大基本定理,编程世界中的基本定理你已经全部学完。

当代计算机学家和数学家已经证明,任何一门拥有变量赋值和基本运算、条件判断、循环和虚拟机的计算机语言都是一个图灵完备系统。何谓图灵完备系统?图灵完备系统是一个资源无限系统,它可以解决任何可计算问题。

我们前面学习了变量、变量赋值与运算、条件判断和循环,也就是说如果你的计算机内存足够大,你可以用这些知识做任何现在人家用计算机做到的事情,从开发一个Rhino一样的绘图程序到开发Windows一样的操作系统,使用这些就行了。你听说过的这些当代高级语言C++、VB、JAVA、PHP都是图灵完备语言,计算机软硬件架构是基于图灵完备思想建立起来的,因此这些语言在底层原理上都是类似的。有些同学疑虑学习Python怕人家使用C++的同学能够实现的东西你不能够实现,从非常严格的理论角度来说,C++能实现的东西没有Python不能够实现的。

但是不要因此而得意洋洋,对于学习编程来讲,你还仅仅开始。你只学习了公理,但相信我的读者没有一个是这样的天才:经典力学三大公理可以独立推导出麦克斯韦方程,热力学方程、混沌理论。你若想较好的掌握这个工具,你在后面需要花时间学习构建函数以对代码重用,学习各种抽象方法,将现实生活中的对象用语言表达, 学习各种必须掌握的经典算法,学习高手代码的写法,学习各种图形学和优化知识以辅助我们的工作和学习。

朋友们加油,我会持续更新!

发表评论

电子邮件地址不会被公开。 必填项已用*标注