7. for循環(huán)证逻,遞歸與switch

轉(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ì)于從fromto的所有數(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
}

forfromto進(jìn)行迭代实檀,第一次迭代numfrom。每次迭代仲锄,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ù)贷屎,ab罢防。

結(jié)合下圖分析這個(gè)過程到底發(fā)生了什么:

下圖是我自己總結(jié)的從視覺上考慮這個(gè)問題的方法,希望大家能夠提供更好的解決方案:

一共有兩個(gè)參數(shù)唉侄,那么我在每個(gè)數(shù)的下面咒吐,都分別標(biāo)記了ab,表示這個(gè)數(shù)字的ab兩個(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)指代。那么晤愧,上圖中可見ab下方大莫,記為:a <- b。這就是一個(gè)ab映射關(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)\)(這里ab的轉(zhuǎn)換用到了上面ab映射關(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)椅文。從上面分析可見喂很,ab都可以作為寄存器,區(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)
}

上面代碼中,我們把ab還有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):

  1. 指示器是遞增的浪藻,而不是尾遞歸中遞減的捐迫。
  2. 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é)

  1. for循環(huán)。
  2. 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í)草姻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钓猬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撩独,更是在濱河造成了極大的恐慌敞曹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跌榔,死亡現(xiàn)場(chǎng)離奇詭異异雁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)僧须,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門纲刀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事示绊《Р浚” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵面褐,是天一觀的道長(zhǎng)拌禾。 經(jīng)常有香客問我,道長(zhǎng)展哭,這世上最難降的妖魔是什么湃窍? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮匪傍,結(jié)果婚禮上您市,老公的妹妹穿的比我還像新娘。我一直安慰自己役衡,他們只是感情好茵休,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著手蝎,像睡著了一般榕莺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棵介,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天钉鸯,我揣著相機(jī)與錄音,去河邊找鬼鞍时。 笑死亏拉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的逆巍。 我是一名探鬼主播及塘,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锐极!你這毒婦竟也來(lái)了笙僚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤灵再,失蹤者是張志新(化名)和其女友劉穎肋层,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翎迁,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栋猖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汪榔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒲拉。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出雌团,到底是詐尸還是另有隱情燃领,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布锦援,位于F島的核電站猛蔽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏灵寺。R本人自食惡果不足惜曼库,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望略板。 院中可真熱鬧凉泄,春花似錦、人聲如沸蚯根。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颅拦。三九已至,卻和暖如春教藻,著一層夾襖步出監(jiān)牢的瞬間距帅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工括堤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留碌秸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓悄窃,卻偏偏與公主長(zhǎng)得像讥电,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轧抗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)恩敌。 張土汪:刷leetcod...
    土汪閱讀 12,747評(píng)論 0 33
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,424評(píng)論 0 17
  • 白先勇的《寂寞的十七歲》横媚,我在那自白式的文字中真真切切體會(huì)到了那種冷到心顫的寂寞纠炮。每個(gè)人生來(lái)都是孤獨(dú)的、寂寞的灯蝴,只...
    張圓詞閱讀 216評(píng)論 0 2
  • 上班路上有個(gè)美術(shù)館恢口,在地鐵站旁邊,美極了穷躁,掩映在綠樹叢中的花園里耕肩。之前總是看到路邊的道旗有展覽的廣告宣傳,之前那么...
    平凡精靈閱讀 300評(píng)論 0 0
  • 在我的懷里 在你的眼里 那里春風(fēng)沉醉 那里綠草如茵 月光把愛戀 灑滿了湖面 兩個(gè)人的篝火 照亮整個(gè)夜晚 多少年以后...
    God王閱讀 329評(píng)論 0 0