7. for循环,递归与switch

转载自http://wanwu.tech/2017/03/08/function-recursive-and-loop/

我们前面已经介绍过一些基本的控制程序流向的方法,这里这里我们介绍更多方法,方便以后使用。

更进一步,还会通过斐波那契数列,进一步熟悉递归和循环的关系。

for循环

for循环可能会成为你以后最常用的循环手段,没有之一。那么,它与while循环有什么不同呢?我们前面的求和函数变为for循环来看下:

使用while循环:

func sum3From(_ from: Int, to: Int) -> Int {
    var result = 0
    var start = from
    
    while start <= to {
        result += start
        start += 1
    }
    
    return result
}


将上面函数变为for循环:

func sum4From(_ from: Int, to: Int) -> Int {
    var result = 0
    
    for num in from...to {
        result += num
    }

    return result
}

上面的for循环代码我们可以试着读出来:对于从fromto的所有数字num,我们采取以下操作。如果想语序更接近上面程序,我们可以使用英语读出:for every number num in the range from from to to, do the following thing(s).

可以感受到,这段代码的可读性也是很好的,在我不介绍基本语法的情况下,可能很多人已经能够猜出怎么使用了。

上面的代码不仅使用了for循环,还使用了一个我们以前没有碰到过的数据类型范围

范围

首先,我们先介绍一下什么是范围(range)类型,然后再分析for语法。

范围分为两种,一种是闭合范围,相当于数学里的“[]”,Swift写为:

let closedRange = 0...5  // 包括(0 1 2 3 4 5)

一种是半开范围,相当于数学里的“[)”,Swift写为:

let halfOpenRange = 0..<5  // 包括(0 1 2 3 4)

范围内的数字必须是递增的,也就是后面的数字要比前面的数字大。

for详解

for循环语法如下:

for 局部常量 in 范围 {
    循环代码
}

对于我们刚才的这段代码:

func sum4From(_ from: Int, to: Int) -> Int {
    var result = 0
    
    for num in from...to {
        result += num
    }

    return result
}

forfromto进行迭代,第一次迭代numfrom。每次迭代,num都会增加,直到成为to为止(因为这里是闭合范围,所以最后一个num等于to)。

在某种条件下才循环

假设我们想要计算一个范围内奇数和,根据现有知识,可以如下操作:

let count = 10
var sum = 0
for i in 1...count {
    if i % 2 == 1 {
        sum += i
    }
}

不过,Swift为我们提供了一种更简洁,可读性更强的写法:

let count = 10
var sum = 0
for i in 1...count where i % 2 == 1 {
    sum += i
}

只有符合where从句的i才会进行循环。

continue

我们前面介绍过一个break语法,现在再介绍一个continue。与break提前终止循环不同,continue只是跳出本次循环。它的功能其实很多情况下都可以用上面的where代替,但是还有些情况你想要更多控制,所以这里看下continue使用方法。

假设一个矩阵,其值是行号与列号的乘积,比如第三行第五列的值是15。现在,我们只想计算奇数行的值,如何用continue实现?

var sum = 0
for row in 0..<8 {
    if row % 2 == 0 {
        continue
    }
    for column in 0..<8 {
        sum += row * column
    }
}

尝试把continue替换为break,理解二者区别。

使用for循环计算斐波那契数列

这部分,使用for循环做一点有趣的事情,计算一下斐波那契数列。

斐波那契数列长这个样: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368........

这个数列从第3项开始,每一项都等于前两项之和。数学上可以写为:

$$ F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2) $$

递归求解

根据斐波那契数列的数学公式,我们很容易就可以写出一个递归的函数:

func fib(_ count: Int) -> Int {
    if count == 0 {
        return 0
    } else if count == 1 {
        return 1
    } else {
        return fib(count - 1) + fib(count - 2)
    }
}

从上面函数可以看出,fib()函数每次都会调用两次自己,这将会是一个巨大的资源消耗。

转换为尾递归

那么我们考虑怎么能把上面的递归变为尾递归,然后变为循环。


注意到fib()函数每次都会调用两次自己,我们将其看成是两个参数,ab

结合下图分析这个过程到底发生了什么:

下图是我自己总结的从视觉上考虑这个问题的方法,希望大家能够提供更好的解决方案:

一共有两个参数,那么我在每个数的下面,都分别标记了ab,表示这个数字的ab两个参数。然后为了画图和思考方便,把\(F(n)=F(n-1)+F(n-2)\)转为\(F(n+1)=F(n)+F(n-1)\)。现在我们分析\(F(n)\)的两个参数。

注意排列方式,而且我已经将每个数字所属的参数用不同颜色的框标注出。那么这个图我是怎么做标记的呢?

如果后面数字的某个参数值没有任何改变地变换为前面数字那个参数的值,那么后面数字的那个参数在前面数字的那个参数正下面。我们需要一个参数指代自己,就是当前值,这里用a来指代。那么,上图中可见ab下方,记为:a <- b。这就是一个ab映射关系。

如果某个参数通过某种运算得到,那么保持其上为空,方便我们自己做记录,这里为了清晰,我并没有做这样的记录。我们需要一个参数作为\(F(n+1)=F(n)+F(n-1)\)运算的结果,用b来指代,即\(b(n)=F(n)\)。那么\(b(n+1)=b(n-1)+b(n)=a(n)+b(n)\)(这里ab的转换用到了上面ab映射关系)。比如b,可以在上面标记为:“a + b”,即表示这个映射为:b <- a + b

这种图示方法比较适合这种参数不复杂的简单情况,从而为解决更复杂问题打好基础。

分析完两个参数,我们再看看上一章说的寄存器和指示器应该用什么来实现。从上面分析可见,ab都可以作为寄存器,区别仅仅是当前数还是后一个数。那么指示器呢?直接使用要计算的斐波那契数列的位数(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作为参数代入iterate(now:next:count:),当指示器(count)为0的时候,返回当前值(now),否则实现上文所说映射关系,进行尾递归。


转换为for循环

我们已经将递归转变为尾递归,下面我们再将其变为for循环:

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
}

上面代码注意两点:

  1. 指示器是递增的,而不是尾递归中递减的。
  2. for循环没有定义显示的局部常量,而是使用了_来代替。

第一点是因为范围(range)只能递增,不能递减,所以这里我们做了相应的调整。
第二点是因为我们只需要一个数字判断它是否在限定的范围内,循环体中并没有用到这个数字,所以可以使用_来代替。

上面代码中,当指示器等于count时,停止循环,并返回now。否则,进入循环并实现上文所说的映射关系。

练习1

试写出下面这个公式的递归,循环函数。

$$ 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

这是一个我们之前学习过的一个打印星期几的程序

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 = "您输入的数字我看不懂"
}
print(dayOfWeek)

现在我们把它变为switch来感受下区别:

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 = "您输入的数字我看不懂"
}
print(dayOfWeek)

是不是感觉清爽了不少?

其中,default是说凡是不符合上面条件的情况,都采用default的操作。

你也可以使用break来结束switch,比如最后default可以写为:

default:
    break
}

如果有其他语言的编程经验,请注意Swift的switch语句不需要每个case都使用break
default不是必需的。

非数字情况

与很多编程语言不同,Swift条件判断可以使用非数字。而且,甚至如果几种情况的结果相同,你可以把他们合起来一起写:

let string = "Dog"
switch string {
case "Cat", "Dog":
    print("宠物")
default:
    print("可能不是宠物")
}

这个例子中,你使用字符串来判断。并且,如果是 "Cat"或"Dog",执行相同代码。

总结

  1. for循环。
  2. switch语句

练习1题提示

映射关系:

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

下一步

更多函数知识。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,657评论 0 33
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,191评论 0 17
  • 白先勇的《寂寞的十七岁》,我在那自白式的文字中真真切切体会到了那种冷到心颤的寂寞。每个人生来都是孤独的、寂寞的,只...
    张圆词阅读 190评论 0 2
  • 上班路上有个美术馆,在地铁站旁边,美极了,掩映在绿树丛中的花园里。之前总是看到路边的道旗有展览的广告宣传,之前那么...
    平凡精灵阅读 269评论 0 0
  • 在我的怀里 在你的眼里 那里春风沉醉 那里绿草如茵 月光把爱恋 洒满了湖面 两个人的篝火 照亮整个夜晚 多少年以后...
    God王阅读 317评论 0 0