轉(zhuǎn)載自http://wanwu.tech/2017/03/08/function-recursive-and-loop/
我們前面已經(jīng)介紹過一些基本的控制程序流向的方法涛碑,這里這里我們介紹更多方法畏线,方便以后使用擎厢。
更進(jìn)一步朵夏,還會(huì)通過斐波那契數(shù)列蔼啦,進(jìn)一步熟悉遞歸和循環(huán)的關(guān)系。
for循環(huán)
for
循環(huán)可能會(huì)成為你以后最常用的循環(huán)手段仰猖,沒有之一捏肢。那么,它與while
循環(huán)有什么不同呢饥侵?我們前面的求和函數(shù)變?yōu)?code>for循環(huán)來(lái)看下:
使用while
循環(huán):
func sum3From(_ from: Int, to: Int) -> Int {
var result = 0
var start = from
while start <= to {
result += start
start += 1
}
return result
}
將上面函數(shù)變?yōu)?code>for循環(huán):
func sum4From(_ from: Int, to: Int) -> Int {
var result = 0
for num in from...to {
result += num
}
return result
}
上面的for
循環(huán)代碼我們可以試著讀出來(lái):對(duì)于從from到to的所有數(shù)字num鸵赫,我們采取以下操作。如果想語(yǔ)序更接近上面程序躏升,我們可以使用英語(yǔ)讀出:for every number num in the range from from to to, do the following thing(s).
可以感受到辩棒,這段代碼的可讀性也是很好的,在我不介紹基本語(yǔ)法的情況下膨疏,可能很多人已經(jīng)能夠猜出怎么使用了一睁。
上面的代碼不僅使用了for
循環(huán),還使用了一個(gè)我們以前沒有碰到過的數(shù)據(jù)類型范圍佃却。
范圍
首先者吁,我們先介紹一下什么是范圍(range)類型,然后再分析for
語(yǔ)法饲帅。
范圍分為兩種复凳,一種是閉合范圍瘤泪,相當(dāng)于數(shù)學(xué)里的“[]”,Swift寫為:
let closedRange = 0...5 // 包括(0 1 2 3 4 5)
一種是半開范圍育八,相當(dāng)于數(shù)學(xué)里的“[)”对途,Swift寫為:
let halfOpenRange = 0..<5 // 包括(0 1 2 3 4)
范圍內(nèi)的數(shù)字必須是遞增的,也就是后面的數(shù)字要比前面的數(shù)字大髓棋。
for詳解
for
循環(huán)語(yǔ)法如下:
for 局部常量 in 范圍 {
循環(huán)代碼
}
對(duì)于我們剛才的這段代碼:
func sum4From(_ from: Int, to: Int) -> Int {
var result = 0
for num in from...to {
result += num
}
return result
}
for
從from到to進(jìn)行迭代实檀,第一次迭代num為from。每次迭代仲锄,num都會(huì)增加劲妙,直到成為to為止(因?yàn)檫@里是閉合范圍,所以最后一個(gè)num等于to)儒喊。
在某種條件下才循環(huán)
假設(shè)我們想要計(jì)算一個(gè)范圍內(nèi)奇數(shù)和镣奋,根據(jù)現(xiàn)有知識(shí),可以如下操作:
let count = 10
var sum = 0
for i in 1...count {
if i % 2 == 1 {
sum += i
}
}
不過怀愧,Swift為我們提供了一種更簡(jiǎn)潔侨颈,可讀性更強(qiáng)的寫法:
let count = 10
var sum = 0
for i in 1...count where i % 2 == 1 {
sum += i
}
只有符合where
從句的i
才會(huì)進(jìn)行循環(huán)。
continue
我們前面介紹過一個(gè)break
語(yǔ)法芯义,現(xiàn)在再介紹一個(gè)continue
哈垢。與break
提前終止循環(huán)不同,continue
只是跳出本次循環(huán)扛拨。它的功能其實(shí)很多情況下都可以用上面的where
代替耘分,但是還有些情況你想要更多控制,所以這里看下continue
使用方法绑警。
假設(shè)一個(gè)矩陣求泰,其值是行號(hào)與列號(hào)的乘積,比如第三行第五列的值是15〖坪校現(xiàn)在渴频,我們只想計(jì)算奇數(shù)行的值,如何用continue
實(shí)現(xiàn)北启?
var sum = 0
for row in 0..<8 {
if row % 2 == 0 {
continue
}
for column in 0..<8 {
sum += row * column
}
}
嘗試把continue
替換為break
卜朗,理解二者區(qū)別。
使用for循環(huán)計(jì)算斐波那契數(shù)列
這部分咕村,使用for
循環(huán)做一點(diǎn)有趣的事情场钉,計(jì)算一下斐波那契數(shù)列。
斐波那契數(shù)列長(zhǎng)這個(gè)樣: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233懈涛,377惹悄,610,987肩钠,1597泣港,2584,4181价匠,6765当纱,10946,17711踩窖,28657坡氯,46368........
這個(gè)數(shù)列從第3項(xiàng)開始,每一項(xiàng)都等于前兩項(xiàng)之和洋腮。數(shù)學(xué)上可以寫為:
$$ F(0)=1箫柳,F(xiàn)(1)=1, F(n)=F(n-1)+F(n-2) $$
遞歸求解
根據(jù)斐波那契數(shù)列的數(shù)學(xué)公式,我們很容易就可以寫出一個(gè)遞歸的函數(shù):
func fib(_ count: Int) -> Int {
if count == 0 {
return 0
} else if count == 1 {
return 1
} else {
return fib(count - 1) + fib(count - 2)
}
}
從上面函數(shù)可以看出啥供,fib()
函數(shù)每次都會(huì)調(diào)用兩次自己悯恍,這將會(huì)是一個(gè)巨大的資源消耗。
轉(zhuǎn)換為尾遞歸
那么我們考慮怎么能把上面的遞歸變?yōu)槲策f歸伙狐,然后變?yōu)檠h(huán)涮毫。
注意到fib()
函數(shù)每次都會(huì)調(diào)用兩次自己,我們將其看成是兩個(gè)參數(shù)贷屎,a和b罢防。
結(jié)合下圖分析這個(gè)過程到底發(fā)生了什么:
下圖是我自己總結(jié)的從視覺上考慮這個(gè)問題的方法,希望大家能夠提供更好的解決方案:
![](https://gengyabc.github.io/assets/images/posts/swift/2017-03-10-more-controll-flow-recursion-loop/1.png)
一共有兩個(gè)參數(shù)唉侄,那么我在每個(gè)數(shù)的下面咒吐,都分別標(biāo)記了a和b,表示這個(gè)數(shù)字的a和b兩個(gè)參數(shù)属划。然后為了畫圖和思考方便恬叹,把\(F(n)=F(n-1)+F(n-2)\)轉(zhuǎn)為\(F(n+1)=F(n)+F(n-1)\)。現(xiàn)在我們分析\(F(n)\)的兩個(gè)參數(shù)榴嗅。
注意排列方式妄呕,而且我已經(jīng)將每個(gè)數(shù)字所屬的參數(shù)用不同顏色的框標(biāo)注出。那么這個(gè)圖我是怎么做標(biāo)記的呢嗽测?
如果后面數(shù)字的某個(gè)參數(shù)值沒有任何改變地變換為前面數(shù)字那個(gè)參數(shù)的值绪励,那么后面數(shù)字的那個(gè)參數(shù)在前面數(shù)字的那個(gè)參數(shù)正下面。我們需要一個(gè)參數(shù)指代自己唠粥,就是當(dāng)前值疏魏,這里用a來(lái)指代。那么晤愧,上圖中可見a在b下方大莫,記為:a <- b
。這就是一個(gè)a和b的映射關(guān)系官份。
如果某個(gè)參數(shù)通過某種運(yùn)算得到只厘,那么保持其上為空烙丛,方便我們自己做記錄,這里為了清晰羔味,我并沒有做這樣的記錄河咽。我們需要一個(gè)參數(shù)作為\(F(n+1)=F(n)+F(n-1)\)運(yùn)算的結(jié)果,用b來(lái)指代赋元,即\(b(n)=F(n)\)忘蟹。那么\(b(n+1)=b(n-1)+b(n)=a(n)+b(n)\)(這里a和b的轉(zhuǎn)換用到了上面a和b的映射關(guān)系)。比如b搁凸,可以在上面標(biāo)記為:“a + b”媚值,即表示這個(gè)映射為:b <- a + b
。
這種圖示方法比較適合這種參數(shù)不復(fù)雜的簡(jiǎn)單情況护糖,從而為解決更復(fù)雜問題打好基礎(chǔ)褥芒。
分析完兩個(gè)參數(shù),我們?cè)倏纯瓷弦徽抡f的寄存器和指示器應(yīng)該用什么來(lái)實(shí)現(xiàn)椅文。從上面分析可見喂很,a和b都可以作為寄存器,區(qū)別僅僅是當(dāng)前數(shù)還是后一個(gè)數(shù)皆刺。那么指示器呢少辣?直接使用要計(jì)算的斐波那契數(shù)列的位數(shù)(count
)就可以了:
func fibIterate(_ count: Int) -> Int {
func iterate(now: Int, next: Int, count: Int) -> Int {
if count == 0 {
return now
} else {
return iterate(now: next, next: now + next, count: count - 1)
}
}
return iterate(now: 0, next: 1, count: count)
}
上面代碼中,我們把a和b還有count作為參數(shù)代入iterate(now:next:count:)
羡蛾,當(dāng)指示器(count)為0的時(shí)候漓帅,返回當(dāng)前值(now),否則實(shí)現(xiàn)上文所說映射關(guān)系痴怨,進(jìn)行尾遞歸忙干。
轉(zhuǎn)換為for循環(huán)
我們已經(jīng)將遞歸轉(zhuǎn)變?yōu)槲策f歸,下面我們?cè)賹⑵渥優(yōu)?code>for循環(huán):
func fibFor(_ count: Int) -> Int {
var now = 0
var next = 1
for _ in 0..<count {
let temp = now
now = next
next = temp + next
}
return now
}
上面代碼注意兩點(diǎn):
- 指示器是遞增的浪藻,而不是尾遞歸中遞減的捐迫。
-
for
循環(huán)沒有定義顯示的局部常量,而是使用了_
來(lái)代替爱葵。
第一點(diǎn)是因?yàn)?strong>范圍(range)只能遞增施戴,不能遞減,所以這里我們做了相應(yīng)的調(diào)整萌丈。
第二點(diǎn)是因?yàn)槲覀冎恍枰粋€(gè)數(shù)字判斷它是否在限定的范圍內(nèi)赞哗,循環(huán)體中并沒有用到這個(gè)數(shù)字,所以可以使用_
來(lái)代替辆雾。
上面代碼中肪笋,當(dāng)指示器等于count
時(shí),停止循環(huán),并返回now
藤乙。否則猜揪,進(jìn)入循環(huán)并實(shí)現(xiàn)上文所說的映射關(guān)系。
練習(xí)1
試寫出下面這個(gè)公式的遞歸湾盒,循環(huán)函數(shù)湿右。
$$ f(n)=\begin{cases}n,& \text{if } n<3\
f(n-1)+2f(n-2)+3f(n-3), & \text{if } n \geq 3 \end{cases} $$
如果有疑問,可以查看本章末尾提示罚勾。
switch
下面再介紹另一種控制程序流向的方式:switch
。
這是一個(gè)我們之前學(xué)習(xí)過的一個(gè)打印星期幾的程序
let number = 1
let dayOfWeek: String
if number == 1 {
dayOfWeek = "星期一"
} else if number == 2 {
dayOfWeek = "星期二"
} else if number == 3 {
dayOfWeek = "星期三"
} else if number == 4 {
dayOfWeek = "星期四"
} else if number == 5 {
dayOfWeek = "星期五"
} else if number == 6 {
dayOfWeek = "星期六"
} else if number == 7 {
dayOfWeek = "星期日"
} else {
dayOfWeek = "您輸入的數(shù)字我看不懂"
}
print(dayOfWeek)
現(xiàn)在我們把它變?yōu)?code>switch來(lái)感受下區(qū)別:
let number = 1
let dayOfWeek: String
switch number {
case 1:
dayOfWeek = "星期一"
case 2:
dayOfWeek = "星期二"
case 3:
dayOfWeek = "星期三"
case 4:
dayOfWeek = "星期四"
case 5:
dayOfWeek = "星期五"
case 6:
dayOfWeek = "星期六"
case 7:
dayOfWeek = "星期日"
default:
dayOfWeek = "您輸入的數(shù)字我看不懂"
}
print(dayOfWeek)
是不是感覺清爽了不少吭狡?
其中尖殃,default
是說凡是不符合上面條件的情況,都采用default
的操作划煮。
你也可以使用break
來(lái)結(jié)束switch
送丰,比如最后default
可以寫為:
default:
break
}
如果有其他語(yǔ)言的編程經(jīng)驗(yàn),請(qǐng)注意Swift的
switch
語(yǔ)句不需要每個(gè)case
都使用break
弛秋。
default
不是必需的器躏。
非數(shù)字情況
與很多編程語(yǔ)言不同,Swift條件判斷可以使用非數(shù)字蟹略。而且登失,甚至如果幾種情況的結(jié)果相同,你可以把他們合起來(lái)一起寫:
let string = "Dog"
switch string {
case "Cat", "Dog":
print("寵物")
default:
print("可能不是寵物")
}
這個(gè)例子中挖炬,你使用字符串來(lái)判斷揽浙。并且,如果是 "Cat"或"Dog"意敛,執(zhí)行相同代碼馅巷。
總結(jié)
-
for
循環(huán)。 -
switch
語(yǔ)句
練習(xí)1題提示
映射關(guān)系:
last <- now
now <- next
next <- 3 * last + 2 * now + next
func myFunc2(count n: Int) -> Int {
func iterate(last: Int, now: Int, next: Int, count: Int) -> Int {
}
return iterate(last: 0, now: 1, next: 2, count: n)
}
下一步
更多函數(shù)知識(shí)草姻。