Think Julia :条件和递归(第五节)

5.条件和递归
本章的主要内容是if语句,它根据程序的状态执行不同的代码。但首先我要介绍两个新的运算符:floor division和modulus。

floor division和modulus
所述地板除法运算符,÷(\div TAB),分两个数字和回合下来为整数。例如,假设电影的运行时间是105分钟。您可能想知道这是多长时间。常规除法返回一个浮点数:

julia> minutes = 105
105
julia> minutes / 60
1.75

但我们通常不会用小数点写小时。楼层除法返回整数小时数,向下舍入:

julia> hours = minutes ÷ 60
1

要获得剩余部分,您可以在几分钟内减去一小时:

julia> remainder = minutes - hours * 60
45

另一种方法是使用模数运算符,%它将两个数字分开并返回余数。

julia> remainder = minutes % 60
45

模数运算符比看起来更有用。例如,您可以检查一个数字是否可以被另一个整除 - 如果x % y为零,则可以x被整除y。

此外,您可以从数字中提取最右边的数字或数字。例如,x % 10产生x(在基数10中)最右边的数字。同样x % 100产生最后两位数字。

布尔表达式

甲布尔表达式是真或假的表达式。以下示例使用运算符==,该运算符比较两个操作数并生成true它们是否相等,false否则:

julia> 5 == 5
true
julia> 5 == 6
false

true并且false是属于该类型的特殊值Bool; 它们不是字符串:

julia> typeof(true)
Bool
julia> typeof(false)
Bool

该==操作员是关系运算符中的一个; 其他人是:

 x != y               # x is not equal to y
      x ≠ y                # (\ne TAB)
      x > y                # x is greater than y
      x < y                # x is less than y
      x >= y               # x is greater than or equal to y
      x ≥ y                # (\ge TAB)
      x <= y               # x is less than or equal to y
      x ≤ y                # (\le TAB)

警告 尽管您可能对这些操作很熟悉,但Julia符号与数学符号不同。常见的错误是使用单个等号(=)而不是双等号(==)。请记住,这=是一个赋值运算符,==是一个关系运算符。没有这样的东西=<或=>。

逻辑运算符

有三个逻辑运算符:(&&和),||(或)和!(不)。这些运算符的语义(含义)与它们在英语中的含义相似。例如,x > 0 && x < 10仅当x大于0 且小于时才为真10。

n % 2 == 0 || n % 3 == 0如果其中一个或两个条件为真,即如果数字可被2 或 3 整除,则为true 。

两者都&&与||右侧相关联,但&&具有更高的优先级||。

最后,!运算符否定布尔表达式,!(x > y)如果x > y为false则为true,即if x小于或等于y。

条件执行

为了编写有用的程序,我们几乎总是需要能够检查条件并相应地改变程序的行为。条件语句赋予我们这种能力。最简单的形式是if声明:

if x > 0
    println("x is positive")
end

后面的布尔表达式if称为条件。如果为true,则缩进语句运行。如果没有,没有任何反应。

if语句与函数定义具有相同的结构:标题后跟以关键字终止的主体end。像这样的语句称为复合语句。

可以在正文中出现的语句数量没有限制。有时候,拥有一个没有语句的正文很有用(通常作为你尚未编写的代码的管理员)。

if x < 0
    # TODO: need to handle negative values!
end

替代执行

该if陈述的第二种形式是“替代执行”,其中有两种可能性,条件决定哪一种运行。语法如下所示:

if x % 2 == 0
    println("x is even")
else
    println("x is odd")
end

如果余数x除以2是0,那么我们知道它x是偶数,并且程序显示适当的消息。如果条件为false,则运行第二组语句。由于条件必须为true或false,因此将运行其中一个备选方案。替代方案称为分支,因为它们是执行流程中的分支。

链式条件

有时候有两种以上的可能性,我们需要两个以上的分支。表达这样的计算的一种方法是链式条件:

if x < y
    println("x is less than y")
elseif x > y
    println("x is greater than y")
else
    println("x and y are equal")
end

同样,只有一个分支将运行。elseif声明数量没有限制。如果有一个else条款,它必须在最后,但不一定是一个。

if choice == "a"
    draw_a()
elseif choice == "b"
    draw_b()
elseif choice == "c"
    draw_c()
end

按顺序检查每个条件。如果第一个为假,则检查下一个,依此类推。如果其中一个为真,则相应的分支运行并且语句结束。即使多个条件为真,也只有第一个真分支运行。

嵌套条件

一个条件也可以嵌套在另一个条件中。我们可以像上面这样在上一节中编写示例:

if x == y
    println("x and y are equal")
else
    if x < y
        println("x is less than y")
    else
        println("x is greater than y")
    end
end

外部条件包含两个分支。第一个分支包含一个简单的语句。第二个分支包含另一个if语句,它有两个自己的分支。这两个分支都是简单的语句,尽管它们也可以是条件语句。

尽管语句的非强制缩进使得结构明显,但嵌套条件很难很快阅读。尽可能避免使用它们。(((缩进)

逻辑运算符通常提供简化嵌套条件语句的方法。例如,我们可以使用单个条件重写以下代码:

if 0 < x
    if x < 10
        println("x is a positive single-digit number.")
    end
end

该print语句仅在我们通过两个条件时运行,因此我们可以与&&运算符获得相同的效果:

if 0 < x && x < 10
    println("x is a positive single-digit number.")
end

对于这种情况,Julia提供了更简洁的选择:

if 0 < x < 10
    println("x is a positive single-digit number.")
end

递归

一个函数调用另一个函数是合法的; 一个函数调用自身也是合法的。可能并不明显为什么这是一件好事,但事实证明这是程序可以做的最神奇的事情之一。例如,查看以下函数:

function countdown(n)
    if n ≤ 0
        println("Blastoff!")
    else
        print(n, " ")
        countdown(n-1)
    end
end

如果n为0或负数,则输出单词; “Blastoff!”否则,输出n,然后调用名为countdown-itself-passing 的函数n-1作为参数。

如果我们这样调用这个函数怎么办?

julia> countdown(3)
3 2 1 Blastoff!

执行countdown开始于n = 3,并且因为n大于0,它输出值3,然后调用自身……

执行countdown开始于n = 2,并且因为n大于0,它输出值2,然后调用自身……

执行countdown开始于n = 1,并且因为n大于0,它输出值1,然后调用自身……

执行countdown开始于n = 0,并且n从不大于0,它输出单词,”Blastoff!”然后返回。

倒计时n = 1返回。

倒计时n = 2返回。

倒计时n = 3返回。

然后你回来了main

调用自身的函数是递归的 ; 执行它的过程称为递归。

另一个例子,我们可以编写一个打印字符串n的函数 倍。

function printn(s, n)
    if n ≤ 0
        return
    end
    println(s)
    printn(s, n-1)
end

如果n <= 0该return语句退出的功能。执行流程立即返回到调用者,并且函数的其余行不会运行。

该函数的其余部分类似于countdown:它显示s然后调用自身显示n - 1s 额外的时间。所以输出行数是1 + (n - 1 ),总计n。

对于像这样的简单示例,使用for循环可能更容易。但是我们稍后会看到很难用for循环编写并且易于使用递归编写的示例,因此最好早点开始。

递归函数的堆栈图

在Stack Diagrams中,我们使用堆栈图来表示函数调用期间程序的状态。相同类型的图可以帮助解释递归函数。

每次调用函数时,Julia都会创建一个包含函数局部变量和参数的框架。对于递归函数,堆栈上可能同时存在多个帧。

这里写图片描述

像往常一样,堆栈的顶部是框架main。它是空的,因为我们没有在其中创建任何变量main或传递任何参数。

四个countdown帧具有不同的参数值n。堆栈的底部,其中n = 0,称为基本情况。它不会进行递归调用,因此不再有帧。

小费 作为练习,绘制一个用于和printn调用的堆栈图。然后编写一个函数,它接受一个函数对象和一个数字,作为参数,并调用给定的函数ns = “Hello”n = 2do_nn 倍。

无限递归

如果递归永远不会达到基本情况,它将继续进行递归调用,并且程序永远不会终止。这被称为无限递归,通常不是一个好主意。这是一个带有无限递归的最小程序:

function recurse()
    recurse()
end

在大多数编程环境中,具有无限递归的程序并不真正永远运行。当达到最大递归深度时,Julia会报告错误消息:

julia> recurse()
ERROR: StackOverflowError:
Stacktrace:
 [1] recurse() at ./REPL[1]:2 (repeats 80000 times)

这个堆栈跟踪比我们在前一章中看到的要大一些。发生错误时,recurse堆栈上有80000 帧!

如果您偶然遇到无限递归,请检查您的函数以确认存在不进行递归调用的基本情况。如果有基本情况,请检查您是否确保达到它。

键盘输入

到目前为止我们编写的程序不接受用户的输入。他们每次都做同样的事情。

Julia提供了一个内置函数readline,用于停止程序并等待用户输入内容。当用户按下RETURN或时ENTER,程序恢复并readline返回用户键入的字符串。

julia> text = readline()
What are you waiting for?
"What are you waiting for?"

在从用户那里获得输入之前,最好打印一个告诉用户输入内容的提示:

julia> print("What...is your name? "); readline()
What...is your name? Arthur, King of the Britons!
"Arthur, King of the Britons!"

分号;允许在同一行上放置多个语句。在REPL中,只有最后一个语句返回其值。

如果您希望用户键入一个整数,您可以尝试将返回值转换为Int64:

julia> println("What...is the airspeed velocity of an unladen swallow?"); speed = readline()
What...is the airspeed velocity of an unladen swallow?
42
"42"
julia> parse(Int64, speed)
42

但是,如果用户键入除数字字符串之外的其他内容,则会出现错误:

julia> println("What...is the airspeed velocity of an unladen swallow? "); speed = readline()
What...is the airspeed velocity of an unladen swallow?
What do you mean, an African or a European swallow?
"What do you mean, an African or a European swallow?"
julia> parse(Int64, speed)
ERROR: ArgumentError: invalid base 10 digit 'W' in "What do you mean, an African or a European swallow?"
[...]

我们稍后会看到如何处理这种错误。

调试

当发生语法或运行时错误时,错误消息包含大量信息,但它可能是压倒性的。最有用的部分通常是:

这是什么样的错误,以及

它发生的地方。

语法错误通常很容易找到,但有一些问题。通常,错误消息指示发现问题的位置,但实际错误可能在代码中较早,有时在前一行。

运行时错误也是如此。假设您正在尝试以分贝计算信噪比。公式是

这里写图片描述

在朱莉娅,你可能会写这样的东西:

signal_power = 9
noise_power = 10
ratio = signal_power ÷ noise_power
decibels = 10 * log10(ratio)
print(decibels)

你得到:

-Inf

这不是您期望的结果。

为了找到错误,打印比率值可能是有用的,结果是0。问题在第3行,它使用地板划分而不是浮点除法。

警告 您应该花时间仔细阅读错误消息,但不要假设他们说的一切都是正确的。

词汇表

地板部门
表示的运算符,÷它将两个数字和向下舍入(向负无穷大)舍入为整数。

模数算子
一个运算符,用百分号(%)表示,对整数有效,当一个数字除以另一个数时返回余数。

布尔表达式
其值的表达式是任一true或false。

关系运算符
一个比较它的操作数的运算符:==,≠(!=), ,>,<(≥),>=和≤(⇐)。

逻辑运算符
结合布尔表达式的运算符之一:( &&和),||(或)和!(不)。

条件陈述
根据某些条件控制执行流程的语句。

条件
条件语句中的布尔表达式,用于确定运行哪个分支。

复合陈述
由标题和正文组成的语句。正文以关键字终止end。


条件语句中的一个替代语句序列。

有条件的
带有一系列替代分支的条件语句。

嵌套条件
条件语句,出现在另一个条件语句的一个分支中。

退货声明
导致函数立即结束并返回调用者的语句。

递归
调用当前正在执行的函数的过程。

基本情况
递归函数中的条件分支,不进行递归调用。

无限递归
递归,没有基本情况,或永远不会到达它。最终,无限递归会导致运行时错误。

演习
练习5-1
该函数time在“纪元”中返回当前的格林威治标准时间,这是用作参考点的任意时间。在UNIX系统上,时代是1970年1月1日。

julia> time()
1.536693351511752e9

编写一个脚本,读取当前时间并将其转换为小时,分钟和秒的时间,以及自纪元以来的天数。

练习5-2
费马的最后定理说没有正整数a,b和c 这样的
这里写图片描述

对于任何n值 大于2。

编写一个名为的函数checkfermat,它接受四个参数a- b,c和n- 并检查Fermat定理是否成立。如果n大于2并且a^n + b^n == c^n程序应该打印,“圣洁抽烟,费马错了!”否则程序应该打印,“不,这不起作用。”

编写提示用户输入值的函数a,b,c和n,将它们转换为整数,并使用checkfermat以检查其是否违反了费马大定理。

练习5-3
如果给你三根棍子,你可能会或可能不会将它们排成三角形。例如,如果其中一根棍子长12英寸而另外两根长度为1英寸,那么你将无法让短棍在中间相遇。对于任何三种长度,都有一个简单的测试,看看是否可以形成三角形:

小费
如果三个长度中的任何一个长度大于另外两个长度的总和,那么就不能形成三角形。否则,你可以。(如果两个长度的总和等于第三个长度,则它们形成所谓的“退化”三角形。)

编写一个名为的函数istriangle,它将三个整数作为参数,并打印“是”或“否”,具体取决于您是否可以使用给定长度的棒形成三角形。

编写一个函数,提示用户输入三个棒长度,将它们转换为整数,并用于istriangle检查具有给定长度的棒是否可以形成三角形。

练习5-4
以下程序的输出是什么?绘制一个堆栈图,显示打印结果时程序的状态。

function recurse(n, s)
    if n == 0
        println(s)
    else
        recurse(n-1, n+s)
    end
end

recurse(3, 0)

如果你这样调用这个函数会发生什么:recurse(-1, 0)?

编写一个文档字符串,解释某人为了使用这个功能而需要知道的一切(而不是别的)。

以下练习使用案例研究:界面设计中ThinkJulia描述的模块:

练习5-5
阅读以下函数,看看是否可以弄清楚它的作用(参见案例研究中的示例:界面设计)。然后运行它,看看你是否正确。

function draw(t, length, n)
    if n == 0
        return
    end
    angle = 50
    forward(t, length*n)
    turn(t, -angle)
    draw(t, length, n-1)
    furn(t, 2*angle)
    draw(t, length, n-1)
    turn(t, -angle)
    forward(-length*n)
end

练习5-6
这里写图片描述

Koch曲线是一个看起来像A Koch曲线的分形。绘制长度为x的Koch曲线你要做的就是

  1. 绘制长度为x的Koch曲线3。
  2. 向左转60度。
  3. 绘制长度为x的Koch曲线3。
  4. 向右转120度。
  5. 绘制长度为x的Koch曲线3。
  6. 向左转60度。
  7. 绘制长度为x的Koch曲线3。

例外情况是x小于3:在这种情况下,你可以画一条长度为x的直线。

  1. 编写一个函数koch,该函数将乌龟和长度作为参数,并使用乌龟绘制具有给定长度的Koch曲线。
  2. 编写一个函数snowflake,绘制三条Koch曲线,绘制雪花轮廓。
  3. Koch曲线可以通过多种方式推广。有关示例,请参阅https://en.wikipedia.org/wiki/Koch_snowflake并实现您的最爱。

猜你喜欢

转载自blog.csdn.net/m0_37696990/article/details/82711046