第三章:julia的函数(learning julia)(完结)

函数构成了任何编程语言不可或缺的一部分,因为它们增加了代码模块性,并使代码比无组织的非函数代码更具可读性。 Julia也不例外,因为它还提供了函数作为内置库的一部分,以及添加用户定义函数的功能。

本章分为不同的部分,每个部分都详细讨论了自己,并为您提供了一个很好的据点。 本章所涵盖的内容清单如下所示,作为您快速参考的一部分:

  • 创建功能
  • 函数参数
  • 匿名函数
  • 多次派遣
  • 递归
  • 内置功能

    完成本章后,您将能够:

  • 在Julia REPL中创建和定义函数以及独立的Julia脚本
  • 清楚地理解和区分各种参数传递方法及其用法
  • 创建递归函数,同时充分了解递归是什么以及如何在Julia中执行
  • 定义匿名函数在您自己的代码中使用最常用的内置Julia函数

Julia中的函数使用function关键字声明,然后是函数体。 另一个关键字,end,put或标记该函数的逻辑结尾。 定义函数的语法可以概括为:

function name()
…
body
…
end

函数的名称必须后跟一个方括号()。 如果不这样做将导致错误。 来自Python等语言的人可能会发现它有点不同,但是当你开始在Julia编码时它会变得容易。 让我们来看看如何在Julia的REPL中定义和使用函数:

julia> function greet()
println("hello world")
end
greet (generic function with 1 method)
julia> greet()
hello world

以下是官方文档中关于功能的说明:

“函数是一个将参数值元组映射到返回值的对象。”

为了更好地展示我们如何创建有用的函数以及如何调用它们,我们创建了一个名为calculator()的函数,它计算计算器的四个基本操作。 现在,只关注结果以及如何创建函数,因为我们将在本章稍后讨论参数传递,我们将在另一章中讨论if … then … else语句等条件:

julia> function calculator(x, y, operation)
if operation == "+"
x+y
elseif operation == "-"
x-y
elseif operation == "*"
x*y
elseif operation == "/"
x/y
else
println("Incorrect operation")
return 0
end
end
calculator (generic function with 1 method)
julia> calculator(10,20, "+")
30
julia> calculator(10,20, "-")
-10
julia> calculator(10,20, "*")
200
julia> calculator(10,20, "/")
0.5

Julia中的函数也可以使用紧凑形式定义。 一个简单的实现可能是:

f(x,y) = x^2 + y^2

对于那些与数学函数密切配合的人来说,这种形式的使用非常方便,因为它非常方便并且节省了大量的开发时间。

要记住的一件重要事情是,尽管朱莉娅的函数与数学函数非常相似,但它们并不是纯粹的数学函数,因为它们可以随着程序的全局状态而改变或受其影响。

特别的!

有时候,在Julia中定义一个函数时,我们也可能包含一个! (不要与函数名称之后的布尔运算符混淆!因为不在Julia中)。 例如,在Julia中,我们有一个名为push的函数! 其工作是在集合的末尾插入一个或多个项目。 在代码中表示,我们有:

push!(collection of items, the item you want to push to the end of
collection)
julia> push!([1, 2, 3], 4)
4-element Array{Int64,1}:
1
2
3
4

但! 在这里真正的代表什么? 这是一个约定,它表示函数实际上可以改变它的输入,换句话说,函数可以修改它的参数。 发生这种情况的一个重要条件是输入是可变的,并且应该能够在创建后进行更改。

任何函数,无论名称如何,都可能会改变参数。 同样,声明了一个带有!的函数可能不会改变论点。 用!定义的函数! 仅仅是一个明确声明函数将改变参数的约定。

所有类型(包括String,Tuples,Int64和float64等)都是不可变的,以及使用immutable关键字定义的所有类型。

函数参数

到目前为止,我们一直在讨论Julia中的函数语法以及如何在需要时创建函数。 当我们讨论函数时,一个非常重要的方面是参数。

毫无疑问,我们几乎在所有其他语言中都使用它们,因为它们可能会被值或引用传递。

但Julia不同。Julia遵循一个惯例,通过分享知道! 等等,现在分享意味着什么呢? 为此,我们首先回到两个最常用的约定。

通过值而不是通过引用传递

当我们说通过值时,它意味着作为参数传递给函数的any的值将被复制到该函数中,这意味着将传递相同变量的两个副本。
另一方面,当我们说通过引用传递时,传递给函数的任何引用或位置也会传递给该函数,这意味着只传递一个变量副本。

通过分享

return语句终止函数的执行并将控制返回给调用函数。 Julia中的函数可能会也可能不会显式使用return语句来返回值。 对于来自其他语言(例如Python)的人来说,这可能会略有不同,但稍后会更容易理解。

如果没有return语句,则会计算并返回最后一个表达式。 例如,底层代码在返回的值方面是相同的:

# function returns the value computed in the last statement
julia> function add_without_return(x,y)
x+y
end
add_without_return (generic function with 1 method)
julia> add_without_return(20,30)
# function with return statement
julia> function add_using_return(x,y)
return x+y
end
add_using_return (generic function with 1 method)
julia> add_using_return(20,30)
50
julia> add_without_return(20,30) == add_using_return(20,30)
true

参数

函数参数是以输入形式传递给函数的变量,以便返回特定的输出。 一个参数函数的一个非常简单的例子如下:

julia> function say_hello(name)
println("hello $name")
end
say_hello (generic function with 1 method)
julia> say_hello("rahul")
hello rahul

这里,say_hello函数接受一个名为name的参数,它是一个字符串。 在调用名为rahul的函数时,hello字符串变为hello rahul。 简单!
要记住的一件事是,尽管Julia是动态类型的,但它支持使用静态类型的变量。 修改前面的代码,我们有:

julia> function say_hello(name::String)
println("hello $name")
end
say_hello (generic function with 1 method)
julia> say_hello("rahul")
hello rahul

您可能已经注意到,函数的行为方式没有变化。 但是,声明传递的参数类型(速度)有一个巨大的好处。 当我们明确介绍Julia与性能相关的增强功能时,我们将在本书后面讨论速度。

参数可以通过多种方式传递给函数。 让我们一一讨论。

无参

有时,我们可能不希望使用任何参数定义函数。 一些函数定义如下:

julia> function does_nothing
end
does_nothing (generic function with 0 methods)

虽然这个函数在这里没有做任何事情,但是会有一些特定的用例,我们只想让函数定义以接口的形式出现。

可变参数

Varargs代表可变参数。 当我们不确定事先传递给函数的参数总数时,这些就派上用场了。 因此,我们希望拥有任意数量的参数。
我们在Julia实现这一目标的方法是使用三个点或……让我们用一个例子来进一步解释:

julia> function letsplay(x,y...)
println(x)
println(y)
end
letsplay (generic function with 1 method)
julia> letsplay("cricket","hockey","tennis")
cricket
("hockey","tennis")

在这里,我们定义了一个letsplay()函数,用于打印我们传递的游戏的名称。 在这里,Julia将这些参数解释为位置并相应地映射它们。
第一个参数x在函数调用时映射到cricket,并在println()函数的帮助下显示。 所以,x=“cricket”。
另一方面,y被解释为因为它最初被传递的元组在声明函数时跟…… 因此,y被映射到hockey和tennis。 所以,y =(“hockey”,“tennis”)

对于熟悉Python的人来说,这听起来与* args作为函数参数传递的概念非常相似。

另一方面,传递给函数的参数可以以多种方式预先声明。 例如,让我们有一个函数,它以下列方式接收可变数量的参数:

julia> x = (1,2,3,4,5)
(1,2,3,4,5)
julia> function numbers(a...)
println("the arguments are -> ",x)
end
numbers (generic function with 1 method)
julia> numbers(x)
the arguments are -> (1,2,3,4,5)

正如你在这里看到的,我们已经传递了一个值元组的函数数:

julia> typeof(x)
Tuple{Int64,Int64,Int64,Int64,Int64}

我们也可以将x作为值列表传递,最终结果仍然不会受到影响。 为了证明这一点,我们将x初始化为数组并重新编写代码。 结果是我们所期望的:

julia> x = [1, 2, 3, 4, 5]
5-element Array{Int64,1}:
1
2
3
4
5
julia> typeof(x)
Array{Int64,1}
julia> numbers(x)
the arguments are -> [1,2,3,4,5]

可选参数

有时,在特定用例的实现过程中,您可能希望修复某些参数(即具有值)或设置为默认值。 例如,您希望将数字转换为另一个数字,其基数为8或可能是16。最适合这种情况的方法是使用一个占用参数库的泛型函数,然后根据 需求将其设置。

因此,您只需拥有convert_to_base(base = 8)或convert_to_base(base = 16),而不是使用convert_to_octal()或convert_to_hex()等函数。

可选参数的一个更简单的示例:

# function f takes 1 mandatory argument and
# 2 optional arguments
julia> function f(x, y=4, z=10)
x+y+z
end
f (generic function with 1 method)
julia> f(10)
24
julia> f(110)
124

我们离开了与读者一起创建函数convert_to_base的练习。

了解函数方面的范围

当我们在Julia中定义函数时,我们也可以在函数体内定义变量。这样,该变量被称为在函数的局部范围内,因此被称为局部变量。 另一方面,任何未在函数体内声明的变量都被称为全局范围,因此称为全局变量。
不同的代码块可以使用相同的名称而不引用相同的实体。 这由范围规则定义。

Julia有两种主要类型的范围,全局范围和本地范围。 本地范围可以嵌套。 除非另有说明,否则模块或REPL中的变量通常在全局范围内。 循环,函数,宏,try-catch-finally块中的变量属于局部范围。

以下示例是解释本地范围:

julia> for i=1:5
hello = i
end
julia> hello
ERROR: UndefVarError: hello not defined

hello仅在for循环的范围内可用,而不在其外部。
我们可以修改上一个函数,让循环外部有hello:

julia> for i=1:5
global hello
hello = i
end
julia> hello
5

Julia使用了一种称为词法作用域的东西,这基本上意味着函数的作用域不会从其调用者的作用域继承,而是从函数定义的作用域继承!

为了更清楚地理解这一点,让我们使用一个例子(我们将在接下来的几节和章节中详细介绍模块)

julia> module Utility
name = "Julia"
tell_name() = name
end
Utility
julia> name = "Python"
"Python"
julia> Utility.tell_name()
"Julia"

在这里,我们创建了一个名为Utility的模块,它包含一个名为name的变量和一个名为tell_name()的函数。 Utility模块内的名称值设置为“Julia”。 此外,我们在Utility模块外面声明了name变量的另一个值,并将其设置为“Python”。

现在,当我们调用Utility.tell_name()时,我们得到“Julia”值。 这表明该函数采用了name变量的值,该变量位于Utility模块中,其中声明了函数tell_me()! 因此,在Utility模块外声明的另一个名称不会影响结果。

Julia还提供了对本地范围的进一步分类,称其为软局部范围或硬局部范围。 这是函数引入的硬局部范围。 我们将在接下来的章节中重新讨论范围,但是现在,我们将继续关注功能范围。

假设我们有一个名为alpha()的函数,它只是将一个名为x的局部变量赋给传递的变量并返回它。 是的,那很简单! 此外,我们已将全局变量定义为x,已将其设置为值:

julia> x = 23
23
julia> function alpha(n::Int64)
x = n
return x
end
alpha (generic function with 1 method)
julia> alpha(25)
25
# global x is unchanged
julia> x
23

因此,如果仔细观察,我们将x的值设置为23,但是当我们调用值为25的alpha()函数时,我们返回25而不是23! 这是因为在函数内声明的变量(即x)在本地分配给作为参数传递的数字(即n)。

但是如果我们想要使用全局声明的相同x呢? 然后我们将使用一个名为global的特殊关键字,它将为我们提供帮助! 请参阅以下代码:

julia> x = 23
23
julia> function alpha(n::Int64)
global x = n
end
alpha (generic function with 1 method)
julia> alpha(25)
25
# global x is now changed
julia> x
25

我们已经删除了为数字指定x的步骤,而是添加了代码,以便使函数为全局可用x指定数字n的值。 结果很明显,因为全局变量x已更改为值25而不是其原始设置值23。

嵌套函数

或者,简单来说,函数中定义的函数。 对于那些来自其他语言背景的人,比如Python,闭包的概念应该很容易适用于Julia。 Closure是一个函数对象,它记住封闭范围中的值,即使它们不在内存中也是如此。

嵌套有助于您想要从最终用户屏蔽该功能的实际实现。

让我们定义一个名为outer的函数,并在其中定义另一个名为inner的函数:

julia> function outer(value_a)
function inner(value_b)
return value_a * value_b
end
end
outer (generic function with 1 method)

如果仔细观察,我们将两个不同的参数传递给两个函数,然后利用它们返回一个连接字符串的值(*用于Julia中的字符串连接)或者乘以两个整数,这取决于参数的数据类型是通过。 此外,暂时还要假设我们传递两个字符串或两个整数并且不混合这些值,因为定义了当前函数,它将引发错误。
在Julia REPL中触发相同的函数,因为我们传递的两个输入都是Int64类型:

julia> result = outer(10)
(::inner) (generic function with 1 method)
julia> typeof(result)
Function
julia> result(10)
100

同样,在数据类型String的函数内传递两个参数,我们有:

julia> result = outer("learning ")
(::inner) (generic function with 1 method)
julia> typeof(result)
Function
julia> result("Julia")
"learning Julia"

在这两种情况下,我们首先将函数的值赋给名为result的变量,然后将第二个参数传递给结果callable。 完成后,我们会按预期获得所需的结果。

匿名函数

匿名函数是常规函数的简写符号。 当函数必须仅使用有限次数时,这些是代码的选择,因此,使用它们而不是使用命名函数可能稍微更容易和更快。 在流行的术语中,它们有时也被称为lambda函数。

要与前面的句子相关联,只需考虑一个场景,其中您希望使用map()函数在值列表上应用功能。 我们可以轻松地定义它们,而不是写下一个完整的函数,而不必费心给它们命名!

在Julia中,我们使用以下语法定义匿名函数:

f -> 2f

语法使用 - >来通知我们在这里定义一个匿名函数。 但是,应该记住,匿名函数本身没有用,因为它们没有名称,因此无法从代码中的任何位置调用:

julia> f ->2f
(::#1) (generic function with 1 method)

让我们考虑使用Julia REPL的示例:

julia> map(f -> 2f, [2,3])
2-element Array{Int64,1}:
4
6
julia> map(f -> 2f, [2 3])
1×2 Array{Int64,2}:
4 6

这里,我们有一个map()函数,它接受两个参数,第一个是函数,后者是可以轻松迭代的值集合。 为了简单起见,我们在这里使用了列表理解。

这里的第一个参数f-> 2f是一个匿名函数,它从列表中获取一个值并将其加倍。

前面的例子纯粹讨论了只接受一个参数的匿名函数。 但是,如果我们有多个呢? 然后我们可能会被迫使用开括号和右括号,即我们函数体内的变量元组:

(f,g) -> 2f + 2g

让我们再次启动Julia REPL会议以深入探讨:

julia> map((f,g) -> 2f + 2g, [2,3], [3,4])
2-element Array{Int64,1}:
10
14

所以,如果你看到这里,我们使用了map()函数,它接受了多个列表理解。 执行此map()函数时,结果为2f + 2g,即2(2)+2(3)= 10和2(3)+2(4)= 14。

多重派发

在我们深入探讨这个主题之前,让我们自问一个简单的问题。 发送是什么意思? 为了用最简单的术语提出答案,我们可以说发送意味着发送!

在编程术语中,dispatch意味着向侦听器发送消息或调用函数。 基本上,将一段数据(或信息包)发送到准备好处理它的代码。

派发可以有许多不同的类型。 从其中一些开始,我们有:

  • 静态分派:分派顺序可以在编译时定义。 实质上,在静态分派中,所有类型在程序执行之前都已知。
    编译器能够为每种可能的数据类型组合生成特定代码,并提前知道它们的使用时间和位置。 这是大多数语言中最常见的一种。 为了分解它,如果我们在代码中有一个位置,使用funct()或x.funct()来调用函数或方法,那么每次都会调用相同的函数;不会有任何变化
  • 动态分派:可以在运行时定义分派顺序。 这只是意味着编译器必须构成所有已定义函数的查找表,然后确定实际调用哪些函数以及在运行时不调用哪些函数。 在代码方面,假设我们有一些类,如classA和classB,并且都有一个名为foo()的函数的实现,然后在运行时,将检查这两个类,最后,它们中的任何一个(classA)可以调用.foo() 或classB.foo())。

  • 多分派:分派顺序取决于函数名称以及传递的参数类型,即函数的签名以及被调用的实际实现直接在运行时确定。
    在代码方面,我们假设我们有classA,它实现了一个名为foo(int)的方法用于整数,foo(char)用于字符类型。
    然后,我们在程序中调用了使用classA.foo(x)调用此函数。 在运行时,我们检查了classA和x,以查看要调用的实际函数。
    朱莉娅支持多次派遣。 我们现在将逐步介绍该主题,并探讨Julia如何实现此技术。

理解方法

方法是朱莉娅生态系统中非常重要的一部分。 为了更好地理解多个调度是什么以及Julia使用它的原因,我们需要首先熟悉方法。

假设我们有一个添加两个数字的函数:

julia> function add_numbers(num1::Int64, num2::Int64)
return num1 + num2
end
add_numbers (generic function with 1 method)

如果仔细观察函数定义,我们已经定义了add_numbers()来接受两个参数。 它们都是整数值(Int64,因为我目前在64位机器上)。 当我们调用函数add_numbers(10,20)时会发生什么? 我们得到了结果如下:

julia> add_numbers(10,20)
30
julia> typeof(ans)
Int64

这里没有惊喜,因为10 + 20 = 30。 另外,请注意我们得到的ans的类型,即Int64,这是预期的。

但是如果我们不小心将浮点值传递给函数呢?

julia> add_numbers(10.0,20.0)
ERROR: MethodError: no method matching add_numbers(::Float64, ::Float64)

Julia抛出一个错误! 但是,等等……为什么?

答案很简单。 我们在函数中明确定义了两个参数 我们将传递给add_numbers()函数的body必须是Int64类型。 如果我们没有明确表示整数类型,我们就不会陷入错误。 只需在此处查看此功能:

julia> function add_without_types(num1,num2)
return num1+num2
end
add_without_types (generic function with 1 method)
julia> add_without_types(10.0,20)
30.0
julia> add_without_types(10,20.0)
30.0
julia> add_without_types(10,20)
30

这似乎与我们在Python中所做的非常接近。 在那里,我们只是定义函数,就像我们在这里所做的那样,并将类型推理部分留给Python的解释器,它与Julia在这里做的工作相同。

但是,这是否意味着我们通过明确定义我们打算采用的参数类型而犯了错误? 绝对不!

定义函数所期望的参数的数据类型会使它们更快,因为编译器不必推断提供给函数的参数类型,因此会阻止编译器浪费时间。 相反,我们获得了良好的速度提升! 当我们讨论如何获得更高性能的Julia代码时,我们将讨论更多关于提高速度的内容。

返回函数add_numbers(num1 :: Int64,num2 :: Int64),想象一下我们想要一个float类型输出的情况,即使我们提供了Int64类型的参数。 一种方法是使用convert,它对提供的参数进行类型转换:

julia> function add_numbers(num1::Int64, num2::Int64)
float_num1 = convert(AbstractFloat, num1)
float_num2 = convert(AbstractFloat, num2)
return float_num1+float_num2
end
add_numbers (generic function with 1 method)
julia> add_numbers(10,20)
30.0

好的,但这不是我们想要的。 对? 相反,我们希望即使在提供float参数时也使函数add_numbers()起作用。 答案是定义另一个处理Float64类型数字的方法:

julia> function add_numbers(num1::Float64, num2::Float64)
return num1+num2
end
add_numbers (generic function with 2 methods)
julia> add_numbers(10.0,20.0)
30.0

是的,您需要创建另一个可以接受Float64类型参数的函数。 这就是我们所说的为函数创建另一种方法。 要查看函数自身的所有方法,我们可以在这里通过add_numbers()函数运行methods():

julia> methods(add_numbers)
# 2 methods for generic function "add_numbers":
add_numbers(num1::Float64, num2::Float64) at REPL[2]:2
add_numbers(num1::Int64, num2::Int64) at REPL[1]:2

仔细观察输出。 它列出了到目前为止我们为函数定义的所有方法。

因此,这样,根据用例,可以为每个功能分配不同的方法。 然后Julia会在运行时根据您传递的参数类型相应地选择这些方法中的任何一个。 这种在运行时选择方法的方法就是我们所说的多分派。

递归

递归是计算机科学中的一种技术,它允许将更大的问题分解为更小的类似子问题,使其更容易解决和调试。

谈到函数,如果函数重复调用自身,我们会调用函数递归。 这通常涉及具有基本条件的功能并且足够小以足以容易地放大自身以解决整体问题。

Julia函数也可以像任何其他语言一样进行递归调用。 让我们斐波纳契数列,其中序列中的每个数字是前两者之和的情况下:

# fibonacci series
1,1,2,3,5,8,13....

因此,如果仔细观察,数字2是前两位数1 + 1的总和,然后下一个数字3是前两个数字1 + 2的总和,依此类推。 这个问题是一个真正的递归案例。

为什么? 仅仅因为每个步骤都涉及使用相同的逻辑,因此可以说整个序列是由这些较小的任务创建的。

让我们在现在Julia代码它,看看我们如何能够实现一个斐波那契数列的任何数n,其中n是序列中元素的总数:

julia> function generate_fibonacci(n::Int64)
if n < 2
return 1
else
return generate_fibonacci(n-1) + generate_fibonacci(n-2)
end
end
generate_fibonacci (generic function with 1 method)

在这个函数中,首先,我们定义基本情况,即阻止函数一遍又一遍地调用自身的条件,这可能导致错误。

一旦完成,我们继续进行其他情况,其中完成了实函数调用,因此,我们得到前两个数的总和,从而导致序列中当前数字的形成。
现在,如果我们只是做一些调用该函数生成斐波纳契数列,我们有:

julia> generate_fibonacci(1)
1
julia> generate_fibonacci(2)
2
julia> generate_fibonacci(3)
3
julia> generate_fibonacci(4)
5
julia> generate_fibonacci(5)
8
julia> generate_fibonacci(6)
13

这就是1,1,3,3,5,13。

回到原来的genrate_fibonacci函数,我们也可以用更短的方式完成它。 Julia使用的这种替代语法更简单易读:

julia> generate_fibonacci(n) = n < 2 ? n : generate_fibonacci(n - 1) +
generate_fibonacci(n - 2)
generate_fibonacci (generic function with 2 methods)
julia> generate_fibonacci(5)
21

但是,当我们在本书后面跳转到条件时,我们将阅读有关此语法的更多信息。

内置函数

Julia提供了许多内置函数,一旦您完全理解Julia基本库的丰富性,这些函数就非常有用。 像所有其他语言一样,Julia具有用户执行的大多数常见任务的功能,以及我们通过本主题时的一些惊喜。

我们现在逐一介绍一些最常见的内置函数
详细示例:

  • workspace():这是一个专门用于Julia REPL的函数,不能在它之外使用。 此函数的工作实际上是清除Julia
    REPL中的当前工作空间,删除用户定义的所有函数,变量,常量或类型,而无需退出REPL并再次重新启动它。
  • typeof():此函数主要用于了解传递给它的参数的数据类型。 对于那些熟悉Python的人来说,这类似于type()函数:
julia> typeof("Julia")
String
julia> typeof(1.0)
Float64
julia> typeof(1)
Int64
julia> typeof(0x23)
UInt8
julia> typeof(0b101)
UInt8
julia> typeof(typeof('julia'))
DataType
  • methods():此函数非常有用,并且在用户想要了解与用户定义的函数相对应的可用方法时经常使用。
    换句话说,当您使用多个分派并想要查询我们对该特定函数的实现时,我们使用此方法:
julia> methods(+)
# 163 methods for generic function "+":
+(x::Bool, z::Complex{Bool}) at complex.jl:136
+(x::Bool, y::Bool) at bool.jl:48
+(x::Bool) at bool.jl:45
+{T<:AbstractFloat}(x::Bool, y::T) at bool.jl:55
+(x::Bool, z::Complex) at complex.jl:143
+(x::Bool, A::AbstractArray{Bool,N<:Any}) at arraymath.jl:126
+(x::Float32, y::Float32) at float.jl:239
+(x::Float64, y::Float64) at float.jl:240
+(z::Complex{Bool}, x::Bool) at complex.jl:137
...
...
  • readline()和readlines():此函数用于从用户接收输入。 有许多方法可以使用此功能。 例如,如果我们想要让用户在Julia
    REPL中输入他/她的名字,那么我们可以使用如下:
julia> name = readline()
"Julia"
"\"Julia\"\n"
julia> println(name)
"Julia

您可能会注意到它将输入作为字符串。 有时,您可能还会遇到提供给此功能的STDIN,它不会影响输出并表示输入到该功能的标准输入。

我们也可以使用相同的函数从文件中读取稍作修改。 在这里,我们只需要传入用户可能想要读取数据的文件的名称:

julia> readline("testfile.csv")
"1.10\n"

但有个问题。 该函数只读取文件中的第一行,忽略其余部分。 您可能会想,这很明显,因为名称表明它只能读取一行。 那么,我们用什么呢? 我们使用readlines()函数读取文件中的所有行:

julia> readlines("testfile.csv")
5-element Array{String,1}:
"1.10\n"
"2.35\n"
"5.56\n"
"7.89\n"
"4.67"
  • enumerate():这是来自不同语言背景的人最常见的函数之一。
    当我们需要迭代一组项目并同时跟踪该特定项目的索引位置时,必须使用enumerate()函数。 一个简单的例子如下所示:
julia> fruits
4-element Array{String,1}:
"apples"
"oranges"
"bananas"
"watermelon"
julia> for (index,fruit) in enumerate(fruits)
println("$index -> $fruit")
end
1 -> apples
2 -> oranges
3 -> bananas
4 -> watermelon
  • parse():这个函数主要用于解析给定的字符串并返回一个表达式。 它基本上可以推断作为字符串传递给自身的参数的数据类型. 要了解这是什么意思,请看下面的示例:
julia> parse("2")
2
julia> parse("2.22")
2.22
julia> parse("Julia")
:Julia

你看得到差别吗? parse()能够推断传递的输入值的数据类型。 但我们怎么知道呢? 让我们使用typeof()函数来确认它,它告诉我们返回值的正确数据类型:

julia> typeof(parse("2"))
Int64
julia> typeof(parse("2.22"))
Float64
julia> typeof(parse("Julia"))
Symbol

正确地将2值推断为Int64,将2.22正确推断为Float64。 但”julia”的值呢? 返回类型显示符号值,这是一种数据类型。 现在,什么是Symbol以及它意味着什么? 当我们阅读更多关于类型的内容时,我们可能会在下一章中学到这一点

使用简单内置函数的示例

让我们来看一个实际的例子,其中我们将更多地讨论在使用Julia时函数如何证明在现实世界中有用。 在我们的软件开发职业生涯中,我们所有人都必须使用CSV文件。 对于那些没有的人,没有什么可担心的 - CSV表示文件格式,其中数据以逗号(,)分隔。

想象一下,我们在我们的家乡位置有一个名为sample.csv的CSV文件,该文件包含夏季城镇的白天和夜晚温度值(以摄氏度为单位),连续七周连续几周。

这是实际文件:

mac-rahul:~$ cat sample.csv
"43.4","32.0"
"44.6","31.4"
"40.1","27.6"
"41.1","28.9"
"44.0","30.0"
"45.6","31.2"
"42.0","27.5"

我们以字符串的形式给出值,并用逗号分隔。 第一个值表示白天温度,第二个值表示同一天晚上的温度。

现在,我们的任务是找到这七天的最高温度,最低温度,平均日温度和平均夜间温度,最后,以CSV的形式将完整的值写在文件中。

首先,我们应该首先看到使用Julia读取此文件的方法。 像往常一样,Julia提供了一个名为readcsv(your_file_name_here)的函数,它接受一个CSV文件作为输入。 以交互模式在Julia REPL上启动它,我们有:

julia> readcsv("sample.csv")
7×2 Array{Float64,2}:
43.4 32.0
44.6 31.4
40.1 27.6
41.1 28.9
44.0 30.0
45.6 31.2
42.0 27.5

仔细检查输出,我们看到我们得到一个Float64类型的7x2数组。 但是你有没有看到Julia如何能够理解值是Float64而不是String类型,因为我们最初是在CSV文件中传递的? 这是因为readcsv()方法在内部使用parse()函数,这有助于推断传递的值的数据类型。

成功读入CSV文件后,我们希望将其分配给名为data的变量,我们将使用该变量进行进一步计算:

julia> data = readcsv("sample.csv")

接下来,我们找到最大值,最小值和平均值,如下所示:

  • 最高温度:这非常简单,因为Julia提供了一个名为maximum的函数,它对一组项目进行操作并从中返回最大值:
julia> max_temp = maximum(data)
45.6

最低温度:同样,我们有一个由Julia专门为此提供的功能,称为最小值:

julia> min_temp = minimum(data)
27.5

平均白天温度:我们通过首先查看我们的数据,仅获取白天温度,然后通过将温度总和除以星期大小来计算平均值来计算平均白天温度。

# First we need to grab all the day time temperature values
# over a period of 7 days
# we can do this as under, notice the index, which starts from
# value 1 and NOT 0
julia> data[1:7]
7-element Array{Float64,1}:
43.4
44.6
40.1
41.1
44.0
45.6
42.0
# next, we need to get the sum of all the values, we do it by
# using the sum() function
julia> total_daytime_temperatures = sum(data[1:7])
300.8
# the we quickly find the size of the array, which is easily
# computed by the size() function
julia> total_size = size(data)[1]
7
# finally, we compute avarage as
julia> day_average = total_daytime_temperatures/total_size
42.971428571428575
# rounding off to 1 decimal places, we have
julia> round(day_average, 1)
43.0

平均夜间温度:这再次与前一部分几乎相似,唯一的例外是数据变量的索引范围,从8开始到14:

julia> data[8:14]
7-element Array{Float64,1}:
32.0
31.4
27.6
28.9
30.0
31.2
27.5
julia> total_nighttime_temperatures = sum(data[8:14])
208.6
julia> night_average = total_nighttime_temperatures/total_size
29.8

最后,我们需要将所有这些数据写入CSV文件。 我们使用writecsv()函数执行此操作。 但是在我们将数据写入CSV文件之前,我们可能希望将目前创建的变量组织成单个项目集合或数组。 一个非常简单直接的方法是创建一个名为list的空数组,其中包含所有四个值:

julia> list = [max_temp, min_temp, day_average, night_average]
4-element Array{Any,1}:
45.6
27.5
43.0
29.8

一旦我们准备好了列表,我们现在可以使用writecsv()函数。 我们将数据写入名为output.csv的文件中:

julia> writecsv("output.csv",list)
Press a semicolon to change to shell mode
shell> cat output.csv
45.6
27.5
43
29.8

‘max’和’min’函数与’maximum’和’minimum’不同。

概要

在本章中,我们看到了如何在Julia中定义函数以及如何使用函数参数。 我们介绍了不同类型的参数传递方法,例如变量参数,单个参数,甚至没有参数。 在本章后面,我们讨论了多重调度如何使Julia成为编程世界中最好的语言之一,因为它提供了速度提升。 有趣的是,我们专注于一个名为递归的主题,它虽然不是特定于函数,但仅仅是为了简要概述递归函数是什么以及如何在Julia中完成递归。 最后,我们介绍了Julia丰富的库提供的一些最常用的内置函数。

既然我们已经掌握了功能是什么以及它们如何授权Julia程序员,我们将会讨论类型,一个非常有趣和重要的主题,其中我们将详细讨论类型,以帮助我们了解它们是什么和 如何在需要时创建一个。


完结于 2018-09-02 0:54

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值