宏
Introduction
[编辑]Julia是最近来到编程语言世界的,她还没有积累大量的介绍性文本。这种情况肯定会改变。很快我们就会看到:
- Julia in 24 hours
- Learn Julia the Hard Way
- Julia for Dummies
- Julia for Fun and Profit
还有其他很多人。但在此之前,这里有一组注释和介绍性段落,这些段落以 wikibook 的形式对 Julia 编程语言进行了温和的介绍。
Wikibooks 的优点和缺点,除了自由和开放之外,是任何人都可以在任何时候编辑任何东西。理论上,随着更多的人添加改进和更正,wikibook 只能变得更好。在实践中,wikibook 可能会失去焦点和一致性,因为它获得了准确性和覆盖范围。但是,因为 Julia 社区已经建立了一种鼓励参与语言发展的良好风气,所以这本维基百科全书是可以自由编辑的,这是正确的,因为每个人都可以自由地编辑这本wikibook。
官方文档 official Julia documentation 非常优秀,尽管它更多地针对的是早期的采用者、开发人员和更有经验的程序员。尽可能多地参考它。
此Wikibook中的大部分文本应与 Julia 的当前版本(截至2018年12月为 1.0 版) 配合使用。
新手入门
[编辑]要在您的计算机上安装 julia,请访问 julia 下载页并按照说明进行操作。安装完毕后,你可以在终端中运行 julia 的解释器,这也被称作使用 REPL。
您可以在浏览器在线使用 julia,例如 NextJournal, Repl.it 和 JuliaBox 都提供了这个服务。JuliaBox 提供了在线的 IJulia notebooks,可以让你使用 Jupyter(原名 IPython)交互式记事本在远程机器上运行 julia。要使用 JuliaBox,您需要使用 Google 或 GitHub 账号登录。
如果你更习惯于在本地运行 julia 程序,你可以使用免费且更强大也更复杂的软件,像 Juno(基于 Atom)和 VisualStudio Code。另一种选择是在 Jupyter 笔记本中通过 IJulia 包使用 julia。Jupyter是一种交互式笔记本技术,它允许您在浏览器窗口中运行 Julia,Python 和 R 的代码。安装并设置 jupyter 笔记本的 julia 环境是直接了当的,但你可能得细心的按照说明来一步步的配置。
使用 julia 的最简洁的方式是:将 julia 安装到本地然后使用 REPL。
在 macOS X 上
[编辑]在 Mac 上,下载 Julia DMG,双击将其打开,然后将图标拖动到 Applications 文件夹。要运行 Julia,可以双击 /Applications 文件夹中 Julia 包的图标。这将打开终端应用程序,并启动一个新窗口。这个窗口就是 Julia 的 REPL 环境,将在下一节中详细介绍:
$ julia _ _ _ _(_)_ | Documentation: https://docs.julialang.org (_) | (_) (_) | _ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 1.0.0 (2018-08-08) _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release |__/ | julia>
或者,您可以在终端中键入如下内容:
$ /Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia
在这里,您将指定位于 Julia 应用程序包中的 Julia 二进制可执行文件的路径名。确切的版本名称可能不同,可使用以下命令查看:
$ ls /Applications/Julia*/Contents/Resources/julia/bin/julia /Applications/Julia-0.4.5.app/Contents/Resources/julia/bin/julia /Applications/Julia-0.4.7.app/Contents/Resources/julia/bin/julia /Applications/Julia-0.5.app/Contents/Resources/julia/bin/julia /Applications/Julia-0.6.app/Contents/Resources/julia/bin/julia /Applications/Julia-0.7.app/Contents/Resources/julia/bin/julia /Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia
直接在终端上运行
[编辑]通常, Julia 安装在 /Applications, 这并不包含在你的 PATH 变量中, 因此如果你在命令行中键入 julia
的话,shell 并不能找到它。
那么要怎么做才能使我们在终端中键入 julia 后能直接打开 julia 呢?下面是一些可选的方法:
第一种方法:在找到Julia二进制可执行文件的位置(请参见上文)后,可以定义以下别名:
alias julia="/Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia"
显然,每次版本号更改时,都必须对其进行更新。
作为一种替代方法,您可以将 /Applications/Julia... 添加到你的 PATH 变量中(操作系统从该变量所指定的路径中查找可执行程序):
PATH="/Applications/Julia-1.0.app/Contents/Resources/julia/bin/:${PATH}" export PATH
另一种方法是创建到可执行文件的链接,并将其放入 /usr/local/bin
目录(该目录应该已经位于您的 PATH 变量中),这样的话键入 julia
与键入 /Applications/Julia/.../julia
就完全等效了。通过下面的命令创建这样的链接:
ln -fs "/Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia" /usr/local/bin/julia
无论选择哪种方法,都可以将相关命令添加到 ~/.bash_profile
文件中,以便在每次创建新 shell 时运行这些命令。
您可以在脚本文件的顶部添加 'shebang' 行,这样 shell 就可以知道这个脚本应该使用 julia 执行:
#!/usr/bin/env julia
在文本编辑器中选择“运行”时,'shebang' 行也有效。如果编辑器在运行文件之前会读取用户的 PATH 变量,julia 代码就能正常运行。(但并非所有的编辑器都会这样做!)
运行 Julia 程序
[编辑]可以通过如下命令运行 Julia 代码文件:
$ julia hello-world.jl
或者在 Julia REPL 内部运行该代码:
$ julia julia> include("hello-world.jl")
如果第一行指定了 Julia 解释器:
#!/Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia
或
#!/usr/bin/env julia
您可以这样运行该文件:
$ ./hello-world.jl
通过 Julia 运行脚本
[编辑]如果您想要在编辑器中编写 Julia 代码并以真正的脚本语言方式运行它,您可以这样做。在脚本文件的顶部添加一行,如下所示:
#!/Applications/Julia-1.0.app/Contents/Resources/julia/bin/julia
路径名指向系统上的正确位置。位于相关 Julia 应用程序包中的某个位置,或者:
#!/usr/bin/env julia
这是 shebang 行。
现在,您可以像运行 Perl 脚本一样从编辑器内部运行 Julia 脚本。
Windows
[编辑]在 Windows 系统上,可以下载 32 位或 64 位的 Julia 自解压文件(.exe
)。双击以开始安装过程。
默认情况下,Julia 会安装到你的 AppData 文件夹。您可以选择使用默认目录,也可以指定为自己的目录(例如 C:\Julia
)。
安装完成后,应创建名为 JULIA_HOME
的系统环境变量,并将其值设置为安装 Julia 的文件夹下的 \bin 目录。
重要的一点是将 JULIA_HOME
指向\bin
目录,而不是安装 julia 的根目录。
然后可以将 ;%JULIA_HOME%
附加到 PATH 系统环境变量中,以便可以从任何目录运行 julia 的脚本。
确保注册表项 HKEY_CURRENT_USER\Environment\Path
为 REG_EXPAND_SZ 类型,以便 %Julia_HOME%
得到正确展开。
FreeBSD
[编辑]在 FreeBSD(包括 TrueOS)或者 DragonFly BSD 上安装 julia,你使用编译好的二进制包或者使用 ports 系统从源码编译安装。
用包管理器安装
[编辑]要安装 julia,直接打开一个终端,然后输入以下命令:
$ pkg install julia
要删除 julia 则输入:
$ pkg remove julia
从 ports 安装
[编辑]如果你的系统中安装了 ports 软件集(你可以通过执行 portsnap auto 命令安装它),以下是编译并安装 julia 的标准方式:
$ cd /usr/ports/lang/julia/ && make install clean
Linux
[编辑]用包管理器安装
[编辑]如果你使用基于 RedHat, Fedora, Debian 或者 Ubuntu 的 Linux 发行版,用系统的包管理器安装 Julia 可以说是最简单的方法。 先从官网下载对应版本的 Julia(或 JuliaPro),然后用你喜欢的方法安装它(一般双击安装包就行)。安装好后,你就能在命令行中使用 Julia 了。你可以键入 julia 以启动 Julia REPL:
$ julia _ _ _ _(_)_ | Documentation: http://docs.julialang.org (_) | (_) (_) | _ _ _| |_ __ _ | Type "?" for "help()", "]?" for Pkg help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version xxxxxxxxxxx _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release |__/ | julia>
Arch Linux
[编辑]在 Arch 上,社区源中就有 julia,可以通过一下命令安装:
$ sudo pacman -S julia
要删除 julia 包及其未被其他软件使用的依赖,请执行:
$ sudo pacman -Rsn julia
Fedora
[编辑]在 Fedora 发行版上,julia 在默认的 updates 源中就有,你可以通过运行以下命令安装:
$ sudo dnf install julia
要删除 julia 包及其未被其他软件使用的依赖,请执行:
$ sudo dnf remove julia
请注意:以上命令仅适用于 Fedora,其他下游的发行版如:RHEL 和 CentOS,请核查他们的发行版,看是否有 julia 的安装包。
使用二进制文件
[编辑]您可以直接从二进制文件中使用 Julia,而无需在您的计算机上安装它。如果您使用的是旧的Linux发行版,或者您没有管理员对机器的访问权限,那么这是非常有用的。 只需从网站下载二进制文件,解压缩到一个目录。在此目录中进入 bin 文件夹然后运行:
$ ./julia
如果程序没有运行权限,请使用以下命令授予此权限:
$ chmod +x julia
基本上,这种方法可以在任何Linux发行版上使用。
通过 Julia 运行脚本
[编辑]要告诉您的操作系统应该使用 Julia 运行脚本,您可以用叫做 shebang 的语法。要执行此操作,只需在脚本的最顶部使用以下行:
#!/usr/bin/env julia
以此作为脚本的第一行,操作系统将在 path 中搜索“Julia”,并使用它运行脚本。
The REPL
[编辑]
REPL 环境
[编辑]默认情况下,键入 julia
将启动一个交互式 REPL,即读取/求值/打印/循环。它允许您在 Julia 代码中键入表达式,并立即在屏幕上看到计算结果。
- Reads what you type (读取你输入的内容)
- Evaluates it (求值)
- Prints out the return value, then (打印出返回值,然后)
- Loops back and does it all over again (返回并周而复始)
REPL 是一个开始尝试这种语言的好地方。但是对于正式代码编写,文本编辑器或交互式笔记本环境 (例如 IJulia/Jupyter) 才是更好的选择。
REPL 的优势在于:它很简单,不需要任何安装或配置就可以工作,还有一个内置的帮助系统。通过它你可以快速的验证一些想法。总而言之,它很适合用于学习 Julia.
使用 REPL
[编辑]键入一些 Julia 代码,然后键入 Return/Enter。Julia 将计算您键入的内容并返回结果:
julia> 42 <Return/Enter> 42 julia>
如果您使用的是 Jupyter (IPython) notebook,则可能必须键入 Control-Enter 或 Shift-Enter.
如果不希望看到打印表达式的结果,请在表达式末尾使用分号:
julia> 42; julia>
此外,如果要访问在 REPL 上键入的最后一个表达式的值,它将存储在变量 ans
中:
julia> ans 42
如果未完成第一行的表达式,请继续键入,直到完成为止。例如:
julia> 2 + <Return/Enter>
Julia 会有耐心地等待着,直到你结束这个表达式:
2 <Return/Enter>
于是乎你就能看到答案:
4 julia>
帮助和查找帮助
[编辑]键入问号 ?
julia> ?
您将立即切换到帮助模式,(在终端中)提示符将变为黄色:
help?>
现在,您可以键入某项的名称(函数名称应不带括号):
help?> exit search: exit atexit textwidth process_exited method_exists indexin nextind IndexLinear TextDisplay istextmime exit(code=0) Stop the program with an exit code. The default exit code is zero, indicating that the program completed successfully. In an interactive session, exit() can be called with the keyboard shortcut ^D. julia>
请注意,帮助系统已尝试查找与您键入的字母匹配的所有单词,并向您显示它找到的内容。
如果要搜索文档,可以使用 apropos
和一个搜索的字符串:
julia> apropos("determinant") LinearAlgebra.det LinearAlgebra.logabsdet LinearAlgebra.logdet
您将看到一个函数列表,其名称或描述包含字符串。
julia> apropos("natural log") Base.log Base.log1p help?> log search: log log2 log1p log10 logging logspace Clong Clonglong Culong Culonglong task_local_storage log(b,x) Compute the base b logarithm of x. Throws DomainError for negative Real arguments.
诸如此类。
Shell 模式
[编辑]如果键入分号
julia> ;
立即切换到 shell 模式:
shell>
(提示变为红色)。在shell模式下,您可以键入任何shell(即非Julia)命令并查看结果:
shell> ls file.txt executable.exe directory file2.txt julia>
然后提示符切换回julia,因此每次想执行shell命令时,都必须键入分号。该模式下可用的命令是系统 shell 的命令。
包管理模式
[编辑]如果键入右方括号作为第一个字符:
julia> ]
立即切换到 package 模式:
v1.0 pkg>
这是您执行包管理任务的地方,例如添加包、测试包等等。
要离开 package 模式,请在空行上按 Backspace 或 CTRL+C。
内置函数和宏
[编辑]以下是在REPL提示中提供的一些有用的交互函数和宏:
- varinfo() – 打印有关 module 中导出的全局变量的信息。
julia> varinfo() name size summary –––––––––––––––– ––––––––––– ––––––––––– Base Module Core Module InteractiveUtils 222.893 KiB Module Main Module ans 1.285 KiB Markdown.MD
- @which – 告诉您将为函数和特定参数调用具体哪个方法:
julia> @which sin(3) sin(x::Real) in Base.Math at special/trig.jl:53
- versioninfo() – 获取 Julia 版本和平台信息:
julia> versioninfo() Julia Version 1.0.0-rc1.0 Commit f92a55a06a (2018-08-07 16:29 UTC) Platform Info: OS: macOS (x86_64-apple-darwin14.5.0) CPU: Intel(R) Core(TM) i5-7500 CPU @ 3.40GHz WORD_SIZE: 64 LIBM: libopenlibm LLVM: libLLVM-6.0.0 (ORCJIT, skylake) Environment: JULIA_EDITOR = mvim
还有一种快速查找版本的方法:
julia> VERSION v"1.0.0"
- edit("pathname") – 启动默认编辑器并打开文件
pathname
进行编辑
- @edit rand() – 启动默认编辑器并打开包含定义了内置函数
rand()
的文件
- less("filename-in-current-directory") – 在当前位置显示文件
- clipboard("stuff") – 复制 "stuff" 到系统剪贴板
- clipboard() – 将剪贴板的内容粘贴到当前 REPL 行中
- dump(x) – 在屏幕上显示有关Julia对象
x
的信息
- names(x) – 获取 module
x
所以导出的名称的数组。
- fieldnames(typeof(x)) – 获取属于类型为
x
的符号的数据字段的数组。
<TAB>
键: 自动补全
[编辑]TAB 键通常能够完成或建议完成您键入其名称的某项内容。例如,如果我键入w
,然后按Tab键(在有多个选项时按两次),则会列出以“w”开头的所有当前可用函数:
julia> w <TAB> wait walkdir which while widemul widen withenv write
这既适用于 Julia 实例,也适用于 shell 和 package 模式。例如,下面是如何从Julia内部导航到某个目录:
shell> cd ~ /Users/me shell> cd Doc <TAB> shell> cd Documents/ shell> ls ...
请记住,您可以使用 ? 并键入其全名(或使用 TAB-补全)来获得有关函数的帮助。
TAB-补全也适用于unicode符号:例如键入 \alp
然后按 TAB 将会得到 \alpha
再按一次 TAB 就会得到 α
。
对于Emoji: 键入 \:fe
然后按 TAB 将会得到 \:ferris_wheel:
再按一次 TAB 就会得到 🎡。
历史记录
[编辑]您可以使用上箭头键和下箭头键回顾以前的命令记录(您可以退出并重新启动,而不会删除该历史记录)。
因此,您不必再次键入长的多行表达式,因为您可以从历史中回忆起它。如果您键入了大量表达式,则可以通过按Ctrl-R和Ctrl-S来前后搜索它们。
变量作用域与性能
[编辑]一个关于 REPL 的警告。REPL 在 Julia 的 全局范围 内运行。
通常,当编写较长的代码时,您会将代码放在函数中,并将函数组织到模块和包中。当您的代码被组织成函数时,Julia的编译器工作得更有效,因此您的代码将运行得更快。
还有一些事情在 top level 是做不到的:比如为变量的值指定类型。
更改提示符并自定义 Julia 会话
[编辑]每次启动Julia时,都会运行以下 Julia 文件(除非使用startup-file=no
选项)。
~/.julia/config/startup.jl
这使您可以加载任何您所知的以后需要的包。例如,如果要自动定制 REPL 会话,可以安装能在启动文件中自定义REPL的外观和行为的包 OhMyREPL.jl (https://github.com/KristofferC/OhMyREPL.jl) ,然后在 startup 文件中:
using OhMyREPL
如果您只想在每次启动 Julia 会话时设置提示,则只需添加以下说明:
using REPL function myrepl(repl) repl.interface = REPL.setup_interface(repl) repl.interface.modes[1].prompt = "julia-$(VERSION.major).$(VERSION.minor)> " return end atreplinit(myrepl)
这只会设置当前的REPL提示符,以显示您的会话正在使用的 Julia 版本号。
Julia 与 数学
[编辑]你把 Julia REPL 当作一个强大的计算器。这也是很好的练习。(这是介绍交互式编程语言的传统,也是认识该语言的好方法。)
输入长数字
[编辑]世界上一半的人使用逗号(,)将长数字分成三组,另一半使用句点(.)。(我们其余的人使用科学记法)。
在Julia中,可以使用下划线(_)分隔:
julia> 1_000_000 - 2_015 997985
尽管你在返回的结果中看不到。
要使用科学符号,只需键入“e”,并记住不要使用空格:
julia> planck_length = 1.61619997e-34
我的电脑有多快?(每秒执行多少个十亿次的浮点运算)
julia> peakflops() / 1e9 48.778354495441356 julia> peakflops() / 1e9 54.20509453559899
(请注意第二次速度更快,这是因为 Julia 的编译起作用了。)
操作符即函数
[编辑]julia> 2 + 2 4 julia> 2 + 3 + 4 9
数字相加的等效形式:
julia> +(2, 2) 4
通常在值之间使用的运算符是普通的 Julia 函数,可以与其他函数用相同的方式使用。类似的:
julia> 2 + 3 + 4 9
可以写成
julia> +(2, 3, 4) 9
以及
julia> 2 * 3 * 4 24
可以写成
julia> *(2,3,4) 24
Julia 还提供了一些数学常量:
julia> pi π = 3.1415926535897...
你可以在 MathConstants 模块上找到一些其他的数学常量:
julia> Base.MathConstants.golden φ = 1.6180339887498... julia> Base.MathConstants.e e = 2.7182818284590...
所有常用操作符均可用:
julia> 2 + 3 - 4 * 5 / 6 % 7 1.6666666666666665
注意运算符的优先级。在这种情况下,它是:
((2 + 3) - ((4 * 5) / 6) % 7)
如果要检查运算符的优先级,请将表达式包含在 :(
和 )
中:
julia> :(2 + 3 - 4 * 5 / 6 % 7) :((2 + 3) - ((4 * 5) / 6) % 7)
(有关这方面的更多信息,请参阅本书的元编程章节).
乘法通常是写成 * 的,但当将变量乘以数字文字时,可以省略这一点:
julia> x = 2 2 julia> 2x + 1 5
julia> 10x + 4x - 3x/2 + 1 26.0
这使得方程式更容易写。
有时需要括号来控制计算顺序:
julia> (1 + sqrt(5)) / 2 1.618033988749895
其他一些需要注意的事项包括:
- ^ 乘方
- % 余数
要生成有理数,请使用两个斜杠(//):
julia> x = 666//999 2//3
也有反除法"\",所以 x/y = y\x.
标准算术运算符还具有特殊的更新版本,您可以使用该版本快速更新变量:
- +=
- -=
- *=
- /=
- \=
- %=
- ^=
例如,在定义了变量 x
之后:
julia> x = 5 5
你可以给它加 2
julia> x += 2 7
然后乘以 100
julia> x *= 100 700
再然后把它减到它模11的值:
julia> x %= 11 7
有对数组起作用的元素级运算符。这意味着可以将两个数组元素乘以元素:
julia> [2,4] .* [10, 20] 2-element Array{Int64,1}: 20 80
数组是 Julia 的基础,在这本书中也有他们自己的章节。
如果对两个整数用 /
进行除法运算,则答案始终是一个浮点数。如果您使用过 Python 2 版本,您会记得 Python 返回一个整数结果。Python 3 现在返回一个浮点数。
Julia提供了一个整数除法运算 ÷ (键入 \div TAB
,或使用函数版本的 div()
。当您希望得到整数结果而不是用 /
返回的浮点时,应使用此选项。
julia> 3 ÷ 2 1 julia> div(3, 2) 1
整型数溢出
[编辑]如果您认为您的计算将突破64位限制,请通过使用 big 函数将操作数存储为大数来选择“大整数”:
julia> 2^64 # oops 0 julia> big(2)^64 # better 18446744073709551616 julia> 2^big(64) # equally better 18446744073709551616
要为Julia程序获得最快的执行速度,您应该了解如何在不引入“类型不稳定”的情况下存储数据和变量。
进制转换
[编辑]当使用 REPL 作为计算器时,这些方便的实用函数可能会很有用。
bitstring() 函数显示数字的文字二进制表示形式,存储如下:
julia> bitstring(20.0) "0100000000110100000000000000000000000000000000000000000000000000" julia> bitstring(20) "0000000000000000000000000000000000000000000000000000000000010100"
请注意,正如您所预期的,浮点“版本”的存储方式是不同的。
要从二进制字符串返回十进制,您可以使用 parse()
,它接受目标类型和数字基数:
julia> parse(Int, "0000011", base=2) 3 julia> parse(Int, "DecaffBad", base=16) 59805531053
若要使用默认10以外的数字基,请使用 string
函数将整数转换为字符串:
julia> string(65535, base=16) "ffff" julia> string(64, base=8) "100"
相反, digits(number, base=b)
返回给定基数的数字位的数组 :
julia> digits(255, base=16) 2-element Array{Int64,1}: 15 15
变量
[编辑]在这个表达式中:
julia> x = 3
x 是一个变量,是数据对象的命名存储位置。在 Julia 中,虽然变量名不能以数字或标点符号开头,但可以按照您的喜好来命名变量。如果需要,可以使用Unicode字符。
若要赋值,请使用单个等号。
julia> a = 1 1 julia> b = 2 2 julia> c = 3 3
要测试相等性,您应该使用 ==
运算符或 isequal()
函数。
在 Julia 中,可以同时给多个变量赋值:
julia> a, b = 5, 3 (5,3)
请注意,此表达式的返回值是一个圆括号内的、逗号分隔的、有序的元素列表:简称元组。
julia> a 5 julia> b 3
数字和变量相乘
[编辑]值得重复的是,您可以在变量名称前面加上一个数字来将其相乘,而不必使用星号(*
)。例如:
julia> x = 42 42 julia> 2x 84 julia> .5x 21.0 julia> 2pi 6.283185307179586
特殊符号
[编辑]JuliaREPL提供了对特殊字符的简单访问,例如希腊字母字符、下标和特殊的数学符号。如果键入反斜杠,则可以键入字符串(通常是等效的LaTeX字符串)以插入相应的字符。例如,如果键入以下内容:
julia> \sqrt <TAB>
Julia 会将 \sqrt 替换为平方根符号:
julia> √
其他一些例子:
\Gamma | Γ |
\mercury | ☿ |
\degree | ° |
\cdot | ⋅ |
\in | ∈ |
在Julia源代码中有一个完整的列表。作为一般原则,在 Julia 中鼓励您查看源代码,因此有一些有用的内置函数可用于查看Julia源文件。例如,在MacOS上,这些符号存储在:
julia> less("/Applications/Julia-1.0.app/Contents/Resources/julia/share/julia/stdlib/v1.0/REPL/src/latex_symbols.jl")
less
通过 pager 运行文件(即 Unix 中的 less命令)如果您很勇敢,请尝试使用 edit() 而不是 less(). 这将启动一个编辑器并打开文件。
还可以在REPL中使用Emoji和其他Unicode字符。
对于emoji,键入Emoji字符名称,在冒号之间,在反斜杠后面,然后按<TAB>:
julia> \:id: <TAB>
会更改为:
julia> 🆔
您可以在 https://docs.julialang.org/en/latest/manual/unicode-input/#Unicode-Input-1 找到列表.
输入不在此列表中的Unicode符号是可能的,但更依赖于操作系统:在MacOS上,在键入Unicode十六进制数字时按住 Ctrl/Alt键(启用Unicode十六进制键盘);在Windows上,输入Ctrl+Shift+u,后跟十六进制数字。)
julia> ✎ = 3 3 julia> ✎ 3
数学函数
[编辑]因为Julia特别适合于科学和技术计算,所以您可以立即使用许多数学函数,而且您通常不必导入它们或使用前缀,它们已经可用了。
三角函数值要求以弧度为单位:
julia> sin(pi / 2) 1.0
但是也有基于度的版本:sind(90) 找到90度的正弦。使用 deg2rad() 和 rad2deg() 在度数和弧度之间进行转换。
还有许多对数函数:
julia> log(12) 2.4849066497880004
和精确的勾股定理函数 hypot()
julia> hypot(3, 4) 5.0
norm() 函数返回向量的 "p" 范数或矩阵的算子范数。下面是 divrem():
julia> divrem(13, 3) # returns the division and the remainder (4,1)
There are dozens of others.
有一个称为ans
的系统范围的变量,它会记住最近的结果,因此您可以在下一个表达式中使用它。
julia> 1 * 2 * 3 * 4 * 5 120 julia> ans/10 12.0
小练习
[编辑]猜猜看,然后使用帮助系统了解 mod2pi() 和 isapprox() 做了什么。
这里描述了作为Julia标准提供的所有函数:[1]
随机数
[编辑]rand() – 获取一个介于0和1之间的随机 Float64 数值。
julia> rand() 0.11258244478647295
rand(2, 2) – 维数为 2, 2 类型为 Float64 的数组
rand(type, 2, 2) – 维数为 2, 2 类型为 type 的数组
rand(range, dims) – 具有指定的范围内(包括两端)的数字数组:
julia> rand(0:10, 6) 6-element Array{Int64,1}: 6 7 9 6 3 10
(有关范围对象的详细信息,请参见数组一章。)
The rand() function can generate a true or false value if you tell it to, by passing the Bool keyword:
如果传递 Bool 关键字给 rand() 函数,它可以生成 true 或 false 值:
julia> rand(Bool) false
或是一堆 true 和 false :
julia> rand(Bool, 20) 20-element Array{Bool,1}: false true false false false true true false false false false false false false true true false true true false
服从分布的随机数
[编辑]randn() 给出正态分布中的一个随机数,平均值为0,标准差为1。randn(n) 给出 n 个这样的数字:
julia> randn() 0.8060073309441075 julia> randn(10), ([1.31598,1.55126,-1.14605,-0.562148,0.69725,0.468769,-1.58275,0.238471,2.72857,1.11561],)
( randn(10) 后面的逗号仅用于行的可视化)
如果已安装 Plots 包,则可以打印以下内容:
julia> using Plots; gr()
julia> histogram(randn(10000), nbins=100)
设置随机数种子
[编辑]Random 包 涵盖了更多的随机函数,如 randperm()
, shuffle()
和 seed!
.。
在使用随机数之前,可以为随机数生成器设定特定值。这确保后续的随机数将遵循相同的序列,如果它们从相同的种子开始。可以使用 seed!() 或 MersenneTwister() 函数为生成器设定种子。
添加 Random 包后,可以执行以下操作:
julia> using Random julia> Random.seed!(10); julia> rand(0:10, 6) 6-element Array{Int64,1}: 6 5 9 1 1 0
julia> rand(0:10, 6) 6-element Array{Int64,1}: 10 3 6 8 0 1
在重新启动Julia后,相同的种子保证相同的随机数。
简单的输入示例
[编辑]下面是一个简单的示例,说明如何编写和运行从键盘读取输入的函数:
julia> function areaofcircle() println("What's the radius?") r = parse(Float64, readline(stdin)) println("a circle with radius $r has an area of:") println(π * r^2) end areaofcircle (generic function with 1 method) julia> areaofcircle() What's the radius? 42 a circle with radius 42.0 has an area of: 5541.769440932395 julia>
Arrays and Tuples
[编辑]
Storage: 数组 和 元组
[编辑]在 Julia 中,相关项的组(related groups)通常存储在数组(Arrays)、元组(Tuples)或字典(Dictionaries)中。
数组可用于存储向量和矩阵。本节集中讨论数组和元组;有关字典的更多信息参见 Dictionaries and Sets。
数组
[编辑]数组是元素的有序集合。它通常用方括号和逗号分隔的项表示。可以创建已满或为空的数组,以及保存不同类型的值或仅限于特定类型的值的数组。
在 Julia 中,数组被用作列表(lists)、向量(vectors)、表(tables)和矩阵(matrices)。
一维数组充当矢量或列表;二维数组可以用作表或矩阵;三维数组和多维数组同样被看作是多维矩阵。
创建数组
[编辑]创建简单数组
[编辑]下面的代码演示如何创建一个简单的一维数组:
julia> a = [1, 2, 3, 4, 5] 5-element Array{Int64,1}: 1 2 3 4 5
Julia 通知您 ("5-element Array{Int64,1}") 表示您创建了一个有 5 个元素(每个元素都是 64 位整数)的一维数组。并将变量 a
绑定到该数组。 请注意,这个过程很智能:例如,如果其中一个元素看起来像一个浮点数,您将得到 Float64 的数组:
julia> a1 = [1, 2, 3.0, 4, 5] 5-element Array{Float64,1}: 1.0 2.0 3.0 4.0 5.0
类似的对于各种字符串:
julia> s = ["this", "is", "an", "array", "of", "strings"] 6-element Array{String,1}: "this" "is" "an" "array" "of" "strings"
返回一个字符串的数组。
以及:
julia> trigfuns = [sin, cos, tan] 3-element Array{Function,1}: sin cos tan
返回一个包含 Julia 函数的数组。
有许多不同的方法来创建数组:
您可以使它们成为空的、未初始化的、满的、基于序列的、稀疏的、密集的等等。这取决于手头的任务。
未初始化的
[编辑]可以使用 Array{type}(dims)
指定数组的类型和维数 (注意是大写的 "A"), 将该类型放入大括号中,并将长度放在括号中。 undef
表示数组尚未初始化为已知的值。
julia> array = Array{Int64}(undef, 5) 5-element Array{Int64,1}: 4520632328 4614616448 4520668544 4520632328 4615451376 4294967362
julia> array3 = Array{Int64}(undef, 2, 2, 2) 2×2×2 Array{Int64,3}: [:, :, 1] = 4452254272 4452255728 4452256400 4456808080 [:, :, 2] = 4456808816 4452255728 4456808816 4452254272
随机的数字提醒您已经创建了一个未初始化的数组,但还没有用任何合理的信息填充它。
包含任意类型元素的数组
[编辑]可以使用不同类型的元素创建数组:
julia> [1, "2", 3.0, sin, pi] 5-element Array{Any, 1}: 1 "2" 3.0 sin π = 3.1415926535897...
在这里,数组有五个元素,但它们是一个奇怪的混合:数字、字符串、函数、常量。因此 Julia 创建了一个类型为 Any 的数组:
julia> typeof(ans) Array{Any,1}
空数组
[编辑]要创建特定类型的数组,还可以使用类型定义和方括号:
julia> Int64[1, 2, 3, 4] 4-element Array{Int64,1}: 1 2 3 4
如果您认为可以通过在声明类型数组时偷偷输入错误类型的值来愚弄 Julia,结果不会如您所愿:
julia> Int64[1, 2, 3, 4, 5, 6, 7, 8, 9, 10.1] ERROR: InexactError()
你也可以用这种方式创建空数组:
julia> b = Int64[] 0-element Array{Int64,1}
julia> b = String[] 0-element Array{String,1}
julia> b = Float64[] 0-element Array{Float64,1}
创建二维数组和矩阵
[编辑]如果在定义阵列时省略逗号,则可以快速创建二维阵列。下面是一个单行、多列数组:
julia> [1 2 3 4] 1x4 Array{Int64,2}: 1 2 3 4
注意第一行输出的 1x4 {...,2}
可以使用分号添加另一行:
julia> [1 2 3 4 ; 5 6 7 8] 2x4 Array{Int64,2}: 1 2 3 4 5 6 7 8
行向量和列向量
[编辑]对比二者: [1,2,3,4,5]
和 [1 2 3 4 5]
.
使用逗号,此数组可以称为“列向量”,由5行和1列组成:
julia> [1, 2, 3, 4, 5] 5-element Array{Int64,1}: 1 2 3 4 5
但使用空格时,此数组可以称为“行向量”,由1行和5列组成:
julia> [1 2 3 4 5] 1x5 Array{Int64,2}: 1 2 3 4 5
注意 {Int64,2}
这里告诉你 这是一个包含 Int64 (有 1 行和 5 列)的二维数组。在这两种情况下,它们都是标准Julia数组。
这样创建的数组可以用作矩阵:
julia> [1 2 3; 4 5 6] 2x3 Array{Int64,2}: 1 2 3 4 5 6
当然,您可以创建具有3个或更多维度的数组/矩阵。
有许多函数允许您一次性完成创建和填充数组。见 Creating and filling an array.
注意 Julia 是如何区分 Array{Float64,1}
和 Array{Float64,2}
:
julia> x = rand(5) 5-element Array{Float64,1}: 0.4821773161183929 0.5811789456966778 0.7852806713801641 0.23626682918327369 0.6777187748570226
julia> x = rand(5, 1) 5×1 Array{Float64,2}: 0.0723474801859294 0.6314375868614579 0.21065681560040828 0.8300724654838343 0.42988769728089804
Julia 提供 Vector
and Matrix
构造函数 。不过这些只是未初始化的一维数组和二维数组的别名:
julia> Vector(undef, 5) 5-element Array{Any,1}: #undef #undef #undef #undef #undef julia> Matrix(undef, 5, 5) 5x5 Array{Any,2}: #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef #undef
使用范围对象 创建数组
[编辑]在 Julia 中, 冒号 (:
) 有很多种用法。一种用途是定义数字的范围(Range) 和序列。可以通过直接键入范围对象来创建该对象:
julia> 1:10 1:10
这种形式下看起来可能不是很有用,但它为 Julia 中任何需要数字范围或序列的工作提供了原材料。
你可以在循环表达式中这么使用:
julia> for n in 1:10 print(n) end 12345678910
或者使用 collect()
来建立一个由这些数字构成的数组:
julia> collect(1:10) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
你也不必起始或结束使用整数:
julia> collect(3.5:9.5) 7-element Array{Float64,1}: 3.5 4.5 5.5 6.5 7.5 8.5 9.5
还有一个范围对象的三块版本 Start:Step:Stop,它允许您指定步长,而非默认的1。
例如,这将构建一个数组,该数组的元素从0到100,每10步执行一次:
julia> collect(0:10:100) 11-element Array{Int64,1}: 0 10 20 30 40 50 60 70 80 90 100
要降序而不是升序,您必须使用负的 step 值:
julia> collect(4:-1:1) 4-element Array{Int64,1}: 4 3 2 1
代替使用 collect()
来创建一个范围的数组, 你可以在最后一个元素使用省略号运算符 (...
) :
julia> [1:6...] 6-element Array{Int64,1}: 1 2 3 4 5 6
(省略号运算符 ...
有时候被叫做 splat 运算符,它代表一系列参数)
然而, collect()
速度更快,并且是将范围转换为数组的推荐方法。
不过您可以在Julia中的许多情况下使用 Range 对象,并且不必总是将它们扩展为数组。
范围 进一步使用
[编辑]另一个有用的函数是 range()
, 它构造一个从开始值到结束值的范围对象,该对象采用一定大小的特定数量的 step。由于 Julia 通过组合关键字 step()
, length()
以及 stop()
来为您计算缺少的部分,因此不必计算所有信息。
例如,要通过 12 步 从 1 到 100,请执行以下操作:
julia> range(1, length=12, stop=100) 1.0:9.0:100.0
或者从1开始走10步,在100点或之前停下来:
julia> range(1, stop=100, step=10) 1:10:91
如果您确实希望它以数组的形式出现,可以使用 Range 对象来构建一个数组:
julia> collect(range(1, length=12, stop=100)) 12-element Array{Float64,1}: 1.0 10.0 19.0 28.0 37.0 46.0 55.0 64.0 73.0 82.0 91.0 100.0
请注意,它提供了 Float64 数组,而不是 整型数组,即使这些值可能是整数。
对于对数范围(有时称为'log space'),你可以用简单的 Range 对象并且用 exp10
函数广播(broadcast)到该范围的所有元素。
julia> exp10.(range(2.0, stop=3.0, length=5)) 5-element Array{Float64,1}: 100.0 177.82794100389228 316.22776601683796 562.341325190349 1000.0
见 Broadcasting and dot syntax.
在 Range 对象中使用 step()
来指出 step 的大小:
julia> step(range(1, length=10, stop=100)) 11.0
如果知道开始和步骤,但不知道结束,并且知道需要多少元素,请使用 range()
:
julia> range(1, step=3, length=20) |> collect 20-element Array{Int64,1}: 1 4 7 10 13 16 19 22 25 28 31 34 37 40 43 46 49 52 55 58
对 Range 中的值 collect
[编辑]如您所见,如果未在 for
循环中使用 Range 对象,(如果需要)则可以使用 collect()
直接从Range对象获取所有值:
julia> collect(0:5:100) 21-element Array{Int64,1}: 0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100
不过,在处理范围之前,不必总是将范围转换为数组,因为通常可以直接迭代。例如,您不必这样写:
for i in collect(1:6)
println(i)
end
1
2
3
4
5
6
因为如果你不用collect()
它也能很好地运行(而且可能更快):
for i in 1:6
println(i)
end
1
2
3
4
5
6
使用推导和生成器创建数组
[编辑]创建数组的一种有用方法是使用推导 (在 Comprehensions 中具体说明),其中每个元素都可以使用小型计算生成。
例如,要创建5个数字的数组,请执行以下操作:
julia> [n^2 for n in 1:5] 5-element Array{Int64,1}: 1 4 9 16 25
使用两个迭代器,可以轻松创建二维阵列或矩阵:
julia> [r * c for r in 1:5, c in 1:5] 5x5 Array{Int64,2}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
您可以在末尾添加 if
测试以筛选(或保留)通过测试的值:
julia> [i^2 for i=1:10 if i != 5] 9-element Array{Int64,1}: 1 4 9 16 36 49 64 81 100
生成器的表达式类似,并且可以类似的方式使用:
julia> collect(x^2 for x in 1:10) 10-element Array{Int64,1}: 1 4 9 16 25 36 49 64 81 100
julia> collect(x^2 for x in 1:10 if x != 1) 9-element Array{Int64,1}: 4 9 16 25 36 49 64 81 100
生成器表达式的优点是,它们在需要时生成值,而不是先构建一个数组来保存它们。
创建和填满一个数组
[编辑]有许多函数允许您创建具有特定内容的数组。当使用二维数组作为矩阵时,这些方法非常有用:
- zeros(m, n)
创建具有m行和n列的零数组/矩阵:
julia> zeros(2, 3) 2x3 Array{Float64,2}: 0.0 0.0 0.0 0.0 0.0 0.0
如有需要可以指定零的类型:
julia> zeros(Int64, 3, 5) 3×5 Array{Int64,2}: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
- ones(m, n)
创建一个全为1的 m 行 n列的数组或矩阵
julia> ones(2, 3) 2x3 Array{Float64,2}: 1.0 1.0 1.0 1.0 1.0 1.0
- rand(m, n)
创建全是随机数的 m 行 n 列的矩阵:
julia> rand(2, 3) 2×3 Array{Float64,2}: 0.488552 0.657078 0.895564 0.0190633 0.0120305 0.772106
- rand(range, m, n)
创建给定范围的随机数矩阵:
julia> rand(1:6, 3, 3) 3x3 Array{Int64,2}: 4 4 1 3 2 3 6 3 3
- randn(m, n)
创建 m 行 n 列矩阵。矩阵中充满均值为0,标准差为1的正态分布随机数。
除了 zeros()
, ones()
等函数之外,还有 trues()
, falses()
, fill()
, 以及 fill!()
函数。
trues()
和 falses()
函数用布尔值 true 或 false 填充数组:
julia> trues(3, 4) 3x4 BitArray{2}: true true true true true true true true true true true true
注意这个结果是 BitArray。
你可以使用 fill()
来创建具有特定值 (即重复数组)的数组,请执行以下操作:
julia> fill(42, 9) 9-element Array{Int64,1}: 42 42 42 42 42 42 42 42 42 julia> fill("hi", 2, 2) 2x2 Array{String,2}: "hi" "hi" "hi" "hi"
fill!()
有一个感叹号标记 (!
, 或者叫"bang") 警告您即将更改现有数组的内容。(这在 Julia 中是一种有效的指示)。
julia> a = zeros(10) 10-element Array{Float64,1}: 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 julia> fill!(a, 42) 10-element Array{Float64,1}: 42.0 42.0 42.0 42.0 42.0 42.0 42.0 42.0 42.0 42.0
让我们“将一系列谬误更改为真”:
julia> trueArray = falses(3,3) 3x3 BitArray{2}: false false false false false false false false false
julia> fill!(trueArray, true) 3x3 BitArray{2}: true true true true true true true true true
julia> trueArray 3x3 BitArray{2}: true true true true true true true true true
你可以使用 range()
函数来创建类似向量的数组,接下来用 reshape()
把它们变为二维数组:
julia> a = reshape(range(0, stop=100, length=30), 10, 3) 10×3 reshape(::StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}, 10, 3) with eltype Float64: 0.0 34.4828 68.9655 3.44828 37.931 72.4138 6.89655 41.3793 75.8621 10.3448 44.8276 79.3103 13.7931 48.2759 82.7586 17.2414 51.7241 86.2069 20.6897 55.1724 89.6552 24.1379 58.6207 93.1034 27.5862 62.069 96.5517 31.0345 65.5172 100.0
结果是一个10乘3的阵列,包含0和100之间的等间距数字。
重复元素以填充数组
[编辑]repeat()
是通过重复较小的数组来创建数组的函数。
其语法的第一个选项是 repeat(A, n, m)
,源数组在第一维(行)中重复n次,在第二维(列)中重复m次。
您不必提供第二个维度,只需提供所需的行数:
julia> repeat([1, 2, 3], 2) 6-element Array{Int64,1}: 1 2 3 1 2 3 julia> repeat([1 2 3], 2) 2x3 Array{Int64,2}: 1 2 3 1 2 3
第二个选项表示额外的列:
julia> repeat([1, 2, 3], 2, 3) 6x3 Array{Int64,2}: 1 1 1 2 2 2 3 3 3 1 1 1 2 2 2 3 3 3 julia> repeat([1 2 3], 2, 3) 2x9 Array{Int64,2}: 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3 1 2 3
repeat()
函数还允许您通过复制源数组的行和列来创建数组。“内部”和“外部”选项确定是否重复行和/或列。例如,INNER=[2,3]生成一个数组,其中每行有两个副本,每列有三个副本:
inner
和 outer
选项 决定是否重复行 和/或 列。例如, inner = [2, 3]
生成一个数组,其中行拷贝两份,列拷贝三分:
julia> repeat([1, 2], inner = [2, 3]) 4x3 Array{Int64,2}: 1 1 1 1 1 1 2 2 2 2 2 2
相比之下, 这里 outer = [2,3]
:
julia> repeat([1, 2], outer = [2, 3]) 4x3 Array{Int64,2}: 1 1 1 2 2 2 1 1 1 2 2 2
注意后者等同于 repeat([1, 2], 2, 3)
。 outer
关键字的一个更有意义的示例是当它与 inner
关键字组合时。
此时,初始矩阵的每一行的每个元素都是行重复的,然后,生成的矩阵的每一行切片都是三列重复的:
julia> repeat([1 2; 3 4], inner=(2, 1), outer=(1, 3)) 4×6 Array{Int64,2}: 1 2 1 2 1 2 1 2 1 2 1 2 3 4 3 4 3 4 3 4 3 4 3 4
Array 构造器
[编辑]我们在前面看到过为您构建特定类型的数组:Array()
:
julia> Array{Int64}(undef, 6) 6-element Array{Int64,1}: 4454517776 4454517808 4454517840 4454517872 4454943824 4455998977
这是未初始化的;看起来奇怪的数字只是分配给新数组之前内存的旧内容。
嵌套数组
[编辑]创建嵌套数组很简单。有时我们要指定一下初始内容:
julia> a = Array[[1, 2], [3,4]] 2-element Array{Array,1}: [1, 2] [3, 4]
Array
构造器 也可以构建 数组的数组:
julia> Array[1:3, 4:6] 2-element Array{Array,1}: [1,2,3] [4,5,6]
您当然可以通过 reshape()
函数只创建一个简单的数组,然后更改其形状:
julia> reshape([1, 2, 3, 4, 5, 6, 7, 8], 2, 4) 2x4 Array{Int64,2}: 1 3 5 7 2 4 6 8
同样的技术也可用于创建三维阵列。下面是一个三维字符串数组:
julia> Array{String}(undef, 2, 3, 4) 2x3x4 Array{String,3}: [:, :, 1] = #undef #undef #undef #undef #undef #undef
[:, :, 2] = #undef #undef #undef #undef #undef #undef
[:, :, 3] = #undef #undef #undef #undef #undef #undef
[:, :, 4] = #undef #undef #undef #undef #undef #undef
每个元素都被设置为 '未定义' — #undef
.
push!()
函数将另一项推送到数组的最后:
julia> push!(a, rand(1:100, 5)) 3-element Array{Array,1}: [1, 2] [3, 4] [4, 71, 82, 60, 48] julia> push!(a, rand(1:100, 5)) 4-element Array{Array,1}: [1,2] [3,4] [4, 71, 82, 60, 48] [4, 22, 52, 5, 14]
或者你想创建一个空的数组:
julia> a = Array{Int}[] 0-element Array{Array{Int64,N} where N,1} julia> push!(a, [1, 2, 3]) 1-element Array{Array{Int64,N} where N,1}: [1, 2, 3] julia> push!(a, [4, 5, 6]) 2-element Array{Array{Int64,N} where N,1}: [1, 2, 3] [4, 5, 6]
你可以用 Vector
作为 Array
的别名:
julia> a = Vector{Int}[[1, 2], [3, 4]] 2-element Array{Array{Int64,1},1}: [1, 2] [3, 4] julia> push!(a, rand(1:100, 5)) 3-element Array{Array{Int64, 1},1}: [1, 2] [3, 4] [12, 65, 53, 1, 82] julia> a[2] 2-element Array{Int64,1}: 3 4 julia> a[2][1] 3
复制数组
[编辑]如果您有一个现有的数组,并且希望创建另一个具有相同维数的数组,则可以使用 similar()
函数:
julia> a = collect(1:10); # hide the output with the semicolon
julia> b = similar(a) 10-element Array{Int64,1}: 4482975872 4482975792 1 4482975952 4482976032 4482976112 3 3 2 4520636161
请注意,数组维度是被复制了,但是值没有被复制,它们是从随机的内存中复制的。
不过,您仍然可以更改类型和尺寸,因此它们不必如此相似:
julia> c = similar(b, String, (2, 2)) 2x2 Array{String,2}: #undef #undef #undef #undef
在任何情况下都有一个 copy()
函数。
矩阵操作:把数组作为矩阵来用
[编辑]在Julia中,二维数组可以用作矩阵。(如果维数和内容允许的话)所有可用于处理数组的函数都可以用作矩阵。
键入矩阵的一种快速方法是使用空格(生成行)分隔元素,并使用分号分隔行。所以:
θ
你也可以这样做:
julia> id = reshape([1, 2, 3, 4], 2, 2) 2×2 Array{Int64,2}: 1 3 2 4
它接受一个标准数组并对其调整形状,使其在两行和两列中运行。请注意,矩阵是逐列填充的。
如果你不使用逗号或分号:
julia> [1 2 3 4]
你将创建一个单行的数组/矩阵
1x4 Array{Int64,2}: 1 2 3 4
在每种情况下,请注意在类型值后面的大括号 ({Int64,2}
) 中的2。这表示二维数组。
可以通过相邻粘贴两个数组来创建数组,如下所示:
julia> [[1, 2, 3], [4, 5, 6]] 2-element Array{Array{Int64,1},1}: [1, 2, 3] [4, 5, 6]
当您省略逗号时,您将在每个列旁边放置列,您将得到以下结果:
julia> [[1, 2, 3] [4, 5, 6]] 3×2 Array{Int64,2}: 1 4 2 5 3 6
访问数组的内容
[编辑]若要访问数组或矩阵的元素,请在数组名称后加上方括号中的元素编号。以下是一维数组:
julia> a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
这是第五个元素:
julia> a[5] 50
第一个元素索引号为1。Julia是从 1 而不是 0 开始对列表和数组中的元素进行索引的语言之一。
(因此,它属于 Matlab、Mathematica、Fortran、Lua 和 Smalltalk 等精英公司,而其他大多数编程语言则坚定地站在基于 0 的索引器的对立面。【译者注:应该是基于1的索引器的对立面?下为原文:】)
(And thus it's in the elite company of Matlab, Mathematica, Fortran, Lua, and Smalltalk, while most of the other programming languages are firmly in the opposite camp of 0-based indexers.)
最后一个元素称为 end (而不是像在其他一些语言中的-1):
julia> a[end] 100
类似地,您可以通过以下方式访问倒数第二个元素
julia> a[end-1] 90
(倒数第三个元素类似,依此类推)。
您可以提供一组索引号,这些索引号分别放在两端的一对括号中:
julia> a[[3,6,2]] 3-element Array{Int64,1}: 30 60 20
或提供一个范围的索引编号:
julia> a[2:2:end] 5-element Array{Int64,1}: 20 40 60 80 100
You can even select elements using true
and false
values:
甚至可以使用true
和 false
值来选择元素:
julia> a[[true, true, false, true, true, true, false, true, false, false]] 6-element Array{Int64,1}: 10 20 40 50 60 80
下面是一个2D数组,其中的行由分号分隔:
julia> a2 = [1 2 3; 4 5 6; 7 8 9] 3x3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 julia> a2[1] 1
如果您只要求2D数组的一个元素,您将得到一个值,就好像该数组是一列地展开的,即先向下,然后交叉。在这种情况下,您将得到4,而不是2:
julia> a2[2] 4
如您所想的请求行,然后列:
julia> a2[1, 2] 2
即行1,列2。下面是第 1 行第 3 列:
julia> a2[1, 3] 3
但是不要以错误的方式获取行/列索引:
julia> a2[1, 4] ERROR: BoundsError: attempt to access 3×3 Array{Int64,2} at index [1, 4] Stacktrace: [1] getindex(::Array{Int64,2}, ::Int64, ::Int64) at ./array.jl:498
顺便说一下,有一种从数组获取元素的替代方法:getindex()
函数:
julia> getindex(a2, 1, 3) 3 julia> getindex(a2, 1, 4) ERROR: BoundsError: attempt to access 3×3 Array{Int64,2} at index [1, 4] Stacktrace: [1] getindex(::Array{Int64,2}, ::Int64, ::Int64) at ./array.jl:498
使用冒号表示每行或每列。例如,下面是“每行第二列”:
julia> a2[:, 2] 3-element Array{Int64,1}: 2 5 8
下面是“第二行,每列”:
julia> a2[2, :] 3-element Array{Int64,1}: 4 5 6
元素级操作和向量化操作
[编辑]许多Julia函数和运算符专为处理数组而设计。这意味着您不必总是对一个数组单独处理每个元素。
一个简单的例子是使用基本的算术运算符。如果其他参数为单一值,则可以直接在数组中使用这些参数:
julia> a = collect(1:10); julia> a * 2 10-element Array{Int64,1}: 2 4 6 8 10 12 14 16 18 20
新数组的每个元素都是原来的乘以2。同样:
julia> a / 100 10-element Array{Float64,1}: 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.1
新数组的每个元素都是原来的除以100。
这些操作被描述为元素操作。
可以在许多运算符的前面加上一个点(.
). 这些与它们的无点版本相同,按元素处理数组元素。
例如,可以使用 .*
来使用乘法函数(*
)。这使您可以将数组或范围按元素相乘:
julia> n1 = 1:6; julia> n2 = 100:100:600; julia> n1 .* n2 6-element Array{Int64,1}: 100 400 900 1600 2500 3600
结果的第一个元素是通过将两个数组的第一个元素相乘得到的结果,以此类推。
除了算术运算符,一些比较运算符也有元素操作的版本。
例如,在循环中使用 .==
来比较两个数组,而不是使用 ==
。
下面是两个由 10 个数字组成的数组,一个是连续的,另一个是无序的,并且进行了元素比较,以查看数组 b 中有多少个元素与数组 a 恰好位于同一位置:
julia> a = 1:10; b=rand(1:10, 10); a .== b 10-element BitArray{1}: true false true false false false false false false false
Broadcasting: dot syntax for vectorizing functions
[编辑]这种使用点的语法将函数元素应用于数组的技术称为 广播。
在函数名后面,开括号前面加一个点,并提供一个数组或范围作为参数。
例如,下面是一个简单的函数,它将两个数字相乘:
julia> f(a, b) = a * b f (generic function with 1 method)
它在两个标量上按预期工作:
julia> f(2, 3) 6
但是,将此函数应用于数组是很容易的。只需使用点语法:
julia> f.([1, 4, 2, 8, 7], 10) 5-element Array{Int64,1}: 10 40 20 80 70
julia> f.(100, 1:10) 10-element Array{Int64,1}: 100 200 300 400 500 600 700 800 900 1000
在第一个示例中,Julia 自动将第二个参数视为数组,保证乘法操作正确。
min() 和 max()
[编辑]注意 max()
和 min()
. 你可能会觉得 max()
可以在数组上使用,就像这样,查找一个最大的元素:
julia> r = rand(0:10, 10) 10-element Array{Int64,1}: 3 8 4 3 2 5 7 3 10 10
但是,不行……
julia> max(r) LoadError: MethodError: no method matching max(::Array{Int64,1}) ...
max
函数返回其参数中最大的一个。要查找数组中最大的元素,可以使用相关函数maximum()
:
julia> maximum(r) 10
可以对两个或更多个数组使用 max()
执行元素检查,返回另一个包含最大值的数组:
julia> r = rand(0:10, 10); s = rand(0:10, 10); t = rand(0:10,10);
julia> max(r, s, t) 10-element Array{Int64,1}: 8 9 7 5 8 9 6 10 9 9
min()
和 minimum()
的行为方式类似。
使最大值适用于数组的一种方法是使用省略号(splat)运算符:
julia> max(r...) 9
可以使用按元素排列的运算符测试数组的每个值,并在单个操作中对其进行更改。下面是一个从0到10的随机整数组:
julia> a = rand(0:10,10, 10) 10x10 Array{Int64,2}: 10 5 3 4 7 9 5 8 10 2 6 10 3 4 6 1 2 2 5 10 7 0 3 4 1 10 7 7 0 2 4 9 5 2 4 2 1 6 1 9 0 0 6 4 1 4 8 10 1 4 10 4 0 5 1 0 4 4 9 2 9 4 10 9 6 9 4 5 1 1 1 9 10 10 1 9 3 2 3 10 4 6 3 2 7 7 5 4 6 8 3 8 0 7 1 0 1 9 7 5
现在可以测试每个值是否等于0,然后仅将这些元素设置为 11,如下所示:
julia> a[a .== 0] .= 11;
julia> a 10x10 Array{Int64,2}: 10 5 3 4 7 9 5 8 10 2 6 10 3 4 6 1 2 2 5 10 7 11 3 4 1 10 7 7 11 2 4 9 5 2 4 2 1 6 1 9 11 11 6 4 1 4 8 10 1 4 10 4 11 5 1 11 4 4 9 2 9 4 10 9 6 9 4 5 1 1 1 9 10 10 1 9 3 2 3 10 4 6 3 2 7 7 5 4 6 8 3 8 11 7 1 11 1 9 7 5
这是因为 a .== 0
返回一个包含 true
和 false
值的数组,然后使用这些值选择要设置为 11 的 a 的元素。
如果您正在对二维矩阵进行计算,您可能需要阅读更多关于矩阵算术的内容: Matrix arithmetic
行与列
[编辑]对于二维数组,可以使用括号、冒号和逗号提取单个行和列或行和列的范围。
对此 table :
julia> table = [r * c for r in 1:5, c in 1:5] 5x5 Array{Int64,2}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
您可以使用以下命令找到一行(请注意逗号):
julia> table[1, :] 1x5 Array{Int64,2}: 5-element Array{Int64,1}: 1 2 3 4 5
您可以得到一个范围内的行,其后跟一个逗号和一个冒号:
julia> table[2:3,:] 2x5 Array{Int64,2}: 2 4 6 8 10 3 6 9 12 15
要选择列,请以冒号开头,后跟逗号:
julia> table[:, 2] 5-element Array{Int64,1}: 2 4 6 8 10
冒号本身访问整个数组:
julia> table[:] 25-element Array{Int64,1}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
要提取列范围,请执行以下操作:
julia> table[:, 2:3] 5x2 Array{Int64,2}: 2 3 4 6 6 9 8 12 10 15
数组中查找元素
[编辑]如果您想知道数组是否包含项,请使用 in()
函数,可以通过两种方式调用该函数:
julia> a = 1:10
julia> 3 in a true
或者作为函数调用:
julia> in(3, a) # needle ... haystack true
有一组以 find 开头的函数,例如 findall()
, findfirst()
, findnext()
, findprev()
和 findlast()
,您可以使用这些函数来获取匹配特定值的数组单元格的索引,或者传入一个判断。其中每一种都有两种或两种以上的形式。
下面是一组小素数:
julia> smallprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
要查找数字的第一个匹配项并获取其索引,可以使用 findfirst()
函数的以下方法:
julia> findfirst(isequal(13), smallprimes) 6
因此,数组中的第一个 13 出现在第六个单元格中:
julia> smallprimes[6] 13
该函数类似于Julia中的许多函数,接受一个函数作为第一个参数。该函数应用于数组的每个元素,如果函数返回true,则返回该元素或其索引。
该函数返回第一个元素的索引。
下面是另一个使用匿名函数的示例:
julia> findfirst(x -> x == 13, smallprimes) 6
findall()
函数返回索引数组,指向应用时函数返回true的每个元素:
julia> findall(isinteger, smallprimes) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
julia> findall(iseven, smallprimes) 1-element Array{Int64,1}: 1
请记住,这些是索引号的数组,而不是实际的单元格值。索引可用于使用标准的方括号语法提取相应的值:
julia> smallprimes[findall(isodd, smallprimes)] 9-element Array{Int64,1}: 3 5 7 11 13 17 19 23 29
鉴于 findfirst()
返回一个数字,即第一个匹配单元格的索引:
julia> findfirst(iseven, smallprimes) 1
julia> smallprimes[findfirst(iseven, smallprimes)] 2
findnext()
函数与 findall()
和 findfirst()
函数非常相似,但它接受一个额外的数字,告诉函数从数组中间的某个位置开始搜索,而不是从头开始。
例如,如果 findfirst(smallprimes,13)
查找数组中数字 13 第一次出现的索引,则可以在findnext()
中使用以下值从此处继续搜索:
julia> findnext(isodd, smallprimes, 1 + findfirst(isequal(13), smallprimes)) 7
julia> smallprimes[ans] 17
findin(A, B)
函数返回数组 A 中的元素可在数组 B 中找到的元素的索引:
julia> findall(in([11, 5]), smallprimes) 2-element Array{Int64,1}: 3 5 julia> smallprimes[3] 5 julia> smallprimes[5] 11
应该注意索引的返回顺序。
Finding out about an array
[编辑]对于二维数组:
julia> a2 = [1 2 3; 4 5 6; 7 8 9] 3x3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9
我们可以使用以下函数了解有关它的更多信息:
ndims()
size()
length()
count()
ndims()
返回维数。向量为1,表为2,依此类推:
julia> ndims(a2) 2
size()
以元组的形式返回数组的行数和列数:
julia> size(a2) (3,3)
length()
告诉您数组包含多少元素:
julia> length(a2) 9
你可以使用 count()
来查找特定值出现的次数。例如,有多少个非零项?
julia> count(!iszero, a2) 9
要查找数组/矩阵的逆、行列式和其他方面,请参见 Manipulating matrices.。
要在索引号 (从 1 到 n
) 和 行/列号 (1:r
, 1:c
) 之间进行转换, 可以使用:
julia> CartesianIndices(a2)[6] CartesianIndex(3, 2)
例如,查找第六个元素的行和列。
在另一个方向上,什么索引号对应于第3行,第2列?
使用与笛卡尔索引相反的线性索引:
julia> LinearIndices(a2)[3, 2] 6
数组比较
[编辑]union()
构建一个新数组,该数组是两个或多个数组的联合或组合。该操作将删除重复项,并且结果包含每个元素的唯一版本:
julia> odds = collect(1:2:10) 5-element Array{Int64,1}: 1 3 5 7 9
julia> evens = collect(2:2:10) 5-element Array{Int64,1}: 2 4 6 8 10
julia> union(odds, evens) 10-element Array{Int64,1}: 1 3 5 7 9 2 4 6 8 10
请注意,新联合的顺序反映了原始顺序。此示例根本不对数字进行排序:
julia> union(1:5, 1:10, 5:-1:-5) 16-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10 0 -1 -2 -3 -4 -5
intersect()
返回一个新数组,该数组是两个或多个数组的交集。结果包含每个元素的一个匹配项,但仅当它出现在每个数组中时:
julia> intersect(1:10, 5:15) 5:10
julia> intersect(5:20, 1:15, 3:12) 5:12
setdiff()
查找两个数组之间的不同,即 是第一个数组中的元素,而不是第二个数组中的元素:
julia> setdiff(1:15, 5:20) 4-element Array{Int64,1}: 1 2 3 4
julia> setdiff(5:20, 1:15) 5-element Array{Int64,1}: 16 17 18 19 20
过滤 filter
[编辑]有一组相关函数可以让您处理数组的元素。
filter()
在元素通过测试时查找并保留这些元素。在这里,我们使用 isodd()
函数 (将其作为不带括号的命名函数传递,而不是带括号的函数调用) 来过滤(保留)所有 数组中是奇数的项。
julia> filter(isodd, 1:10) 5-element Array{Int64,1}: 1 3 5 7 9
和许多 Julia 函数一样,有一个版本可以更改数组。因此 filter()
返回原始数据的副本,但 filter!()
改变了数组。
我们前面遇到的 count()
函数类似于 filter()
, 但只计算满足条件的元素数:
julia> count(isodd, 1:100) 50
另外,any()
函数只告诉您任何元素是否满足条件:
julia> any(isodd, 1:100) true
all()
函数告诉您是否所有元素都满足条件。在这里 all()
检查 filter()
是否正确地完成了这项工作。
julia> all(isodd, filter(isodd, 1:100)) true
随机元素
[编辑]要从阵列中选择随机元素,请执行以下操作:
julia> a = collect(1:100); julia> a[rand(1:end)] 14
其他函数
[编辑]数组是 Julia 的基础,这里无法具体描述几十个数组处理函数。但这里有几个选择:
找到数组中的极值:
julia> a = rand(100:110, 10) 10-element Array{Int64,1}: 109 102 104 108 103 110 100 108 101 101
julia> extrema(a) (100,110)
findmax()
查找最大元素并在元组中返回它及其索引:
julia> findmax(a) (110,6)
例如 sum()
, prod()
, mean()
, middle()
, 会执行你所期望的操作:
(mean()
和 middle()
已移到标准库的 Statistics 模块中,您可能需要首先输入 "using Statistics
" 才能使用它们)
julia> sum(a) 1046
julia> prod(1:10) 3628800
julia> mean(a) 104.6
julia> middle(a) 105.0
sum()
, mean()
, and prod()
还允许提供函数:将该函数应用于每个元素,然后对结果求和/平均值/积:
julia> sum(sqrt, 1:10) # the sum of the square roots of the first 10 integers 前10个整数的平方根之和 22.4682781862041 julia> mean(sqrt, 1:10) # the mean of the square roots of the first 10 integers 前十个证书的平方根的平均值 2.24682781862041
Combinatorics.jl 包中的函数允许您查找数组的组合和排列。 combinations()
查找数组中所有可能的元素组合:您可以指定每个组合中的元素数:
julia> Pkg.add("Combinatorics") # (do this just once) julia> using Combinatorics julia> collect(combinations(a, 3)) 120-element Array{Array{Int64,1},1}: [109,102,104] [109,102,108] [109,102,103] [109,102,110] [109,102,100] [109,102,108] [109,102,101] [109,102,101] [109,104,108] [109,104,103] [109,104,110] [109,104,100] [109,104,108] ⋮ [103,108,101] [103,101,101] [110,100,108] [110,100,101] [110,100,101] [110,108,101] [110,108,101] [110,101,101] [100,108,101] [100,108,101] [100,101,101] [108,101,101]
以及 permutations()
生成所有的排列。实际上,您可能不需要使用 collect()
将项收集到数组中:
julia> length(permutations(a)) 3628800
改变数组内容: 添加删除元素
[编辑]要在数组末尾添加项,请使用 push!()
:
julia> a = collect(1:10); push!(a, 20) 11-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10 20
与往常一样,感叹号会提醒您此函数会更改数组。你只能将其 push 到向量的末端。
要在前面添加元素,请使用 pushfirst!()
:
julia> pushfirst!(a, 0) 12-element Array{Int64,1}: 0 1 2 3 4 5 6 7 8 9 10 20
若要在给定索引处将元素插入到数组中,请使用 splice!()
例如,下面是一个明显有遗漏的数字列表:
julia> a = [1, 2, 3, 5, 6, 7, 8, 9] 8-element Array{Int64,1}: 1 2 3 5 6 7 8 9
使用 splice!()
在特定的索引值范围内插入序列。Julia返回已替换的值。数组会增长以容纳新的元素,并且在序列插入之后,元素会被向下推。让我们在位置 4:5 的地方插入范围 4:6
:
julia> splice!(a, 4:5, 4:6) 2-element Array{Int64,1}: 5 6
您可能会尝试检查是否正确插入了新值:
julia> a 9-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9
现在,如果要在特定的索引间位置插入一些值,则必须使用称为“空范围”的功能。在这种情况下,索引 n-1 和 n 之间的间隔表示为 n:n-1
。
例如:
julia> L = ['a','b','f'] 3-element Array{Char,1}: 'a' 'b' 'f'
julia> splice!(L, 3:2, ['c','d','e']) 0-element Array{Char,1}
julia> L 6-element Array{Char,1}: 'a' 'b' 'c' 'd' 'e' 'f'
删除元素
[编辑]如果不提供替换,也可以使用 splice!()
来删除元素并移动其余元素。
julia> a = collect(1:10); julia> splice!(a,5); julia> a 9-element Array{Int64,1}: 1 2 3 4 6 7 8 9 10
要删除最后一项,请执行以下操作:
julia> pop!(a) 10
删除第一项:
julia> popfirst!(a) 1
More aggressive modification of arrays (and similar data structures) can be made with functions such as deleteat!()
and splice!()
. You can find out the indices of elements in various ways. Once you know the indices, you can use deleteat!()
to delete an element, given its index number:
julia> a = collect(1:10);
julia> findfirst(isequal(6), a) 4
julia> deleteat!(a, findfirst(isequal(6), a)) 9-element Array{Int64,1}: 1 2 3 4 5 7 8 9 10
deleteat!()
also accepts a range or iterator to specify the indices, so you can do this:
julia> deleteat!(a, 2:6) 4-element Array{Int64,1}: 1 8 9 10
Remember that you can always remove a group of elements using a filter: see Filtering.
其他函数
[编辑]If you want to do something to an array, there's probably a function to do it, and sometimes with an exclamation mark to remind you of the potential consequences. Here are a few more of these array-modifying functions:
resize!()
change the length of a Vectorappend!()
push a second collection at the back of the first oneprepend!()
insert elements at the beginning of the first Vectorempty!(a)
remove all elementsrotr90(a)
make a copy of an array rotated 90 degrees clockwise:
julia> rotr90([1 2 3 ; 4 5 6]) 3x2 Array{Int64,2}: 4 1 5 2 6 3
circshift(a)
move the elements around 'in a circle' by a number of steps:
julia> circshift(1:6, 1) 6-element Array{Int64,1}: 6 1 2 3 4 5
This function can also do circular shifts on 2D arrays too. For example, here's a table:
julia> table = collect(r*c for r in 1:5, c in 1:5) 5×5 Array{Int64,2}: 1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25
By supplying a tuple you can move rows and columns. For example: moving the columns by 0 and the rows by 1 moves the first dimension by 0 and the second by 1. The first dimension is downwards, the second rightwards:
julia> circshift(table, (0, 1)) 5×5 Array{Int64,2}: 5 1 2 3 4 10 2 4 6 8 15 3 6 9 12 20 4 8 12 16 25 5 10 15 20
There's a modifying version of circshift()
, circshift!
设置数组内容
[编辑]To set the contents of an array, specify the indices on the left-hand side of an assignment expression:
julia> a = collect(1:10); julia> a[9]= -9 -9
To check that the array has really changed:
julia> print(a) [1,2,3,4,5,6,7,8,-9,10]
You can set a bunch of elements at the same time, using the broadcasting assignment operator:
julia> a[3:6] .= -5 4-element view(::Array{Int64,1}, 3:6) with eltype Int64: -5 -5 -5 -5 julia> print(a) [1,2,-5,-5,-5,-5,7,8,-9,10]
And you can set a sequence of elements to a suitable sequence of values:
julia> a[3:9] = collect(9:-1:3) 7-element Array{Int64,1}: 9 8 7 6 5 4 3
Notice here that, although Julia shows the 7 element slice as the return value, in fact the whole array has been modified:
julia> a 10-element Array{Int64,1}: 1 2 9 8 7 6 5 4 3 10
You can set ranges to a single value in one operation using broadcasting:
julia> a[1:5] .= 0 0
julia> a 10-element Array{Int64,1}: 0 0 0 0 0 6 7 8 9 10
julia> a[1:10] .= -1; -1
julia> print(a) [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]
As an alternative to the square bracket notation, there's a function call version that does the same job of setting array contents, setindex!()
:
julia> setindex!(a, 1:10, 10:-1:1) 10-element Array{Int64,1}: 10 9 8 7 6 5 4 3 2 1
You can refer to the entire contents of an array using the colon separator without start and end index numbers, i.e. [:]
. For example, after creating the array a
:
julia> a = collect(1:10);
we can refer to the contents of this array a
using a[:]
:
julia> b = a[:] 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10 julia> b[3:6] 4-element Array{Int64,1}: 3 4 5 6
把数组传递给函数
[编辑]函数不能够改变传递进来的参数的值,但是可以对传递进来的 容器 的内容进行修改。
请考虑以下函数,该函数将其参数更改为5:
julia> function set_to_5(x) x = 5 end set_to_5 (generic function with 1 method)
julia> x = 3 3
julia> set_to_5(x) 5
julia> x 3
尽管函数里面的 x
被改变了 ,函数外面的 x
也没有变。函数里面的变量名是局部的。
但是,你可以改变一个容器的内容,比如数组。下面一个函数使用 语法来获取 容器 x
的内容,而不是直接改变 变量x
的值。
julia> function fill_with_5(x) x[:] .= 5 end fill_with_5 (generic function with 1 method)
julia> x = collect(1:10) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
julia> fill_with_5(x) 5
julia> x 10-element Array{Int64,1}: 5 5 5 5 5 5 5 5 5 5
If, instead of accessing the container variable's contents, you try to change the variable itself, it won't work. For example, the following function definition creates an array of 5s in temp
and then attempts to change the argument x
to be temp
.
julia> function fail_to_fill_with_5(x) temp = similar(x) for i in 1:length(x) temp[i] = 5 end x = temp end fail_to_fill_with_5 (generic function with 1 method)
julia> x = collect(1:10) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
julia> fail_to_fill_with_5(x) 10-element Array{Int64,1}: 5 5 5 5 5 5 5 5 5 5
It looks like it worked, but:
julia> x 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
You can change elements of the array, but you can't change the variable so that it points to a different array. In other words, your function isn't allowed to change the binding between the argument and the array that was passed to it.
Julia's way of handling function arguments is described as “pass-by-sharing”. An array isn't copied when you pass it to a function (that would be very inefficient for large arrays).
矩阵运算
[编辑]For matrix-on-matrix arithmetic action, you can:
- add (+) and subtract (-):
θ
θ
θ
θ
- multiply (*), assuming the dimensions are compatible, so m1 * m2
is possible if last(size(m1)) == first(size(m2))
. Note the difference between matrix multiplication and elementwise matrix multiplication. Here's a matrix A
:
θ
and here's matrix B
:
θ
The .*
broadcasting operator multiplies them elementwise:
θ
Compare this with matrix multiplication, A * B
:
θ
which is:
julia> [1 * 10 + 2 * 12 1 * 11 + 2 * 13 ; 3 * 10 + 4 * 12 3 * 11 + 4 * 13] 2x2 Array{Int64,2}: 34 37 78 85
- division of two matrices. You can use the backslash (\) for left division:
julia> A = rand(1:9, 3, 3) 3x3 Array{Int64,2}: 5 4 3 8 7 7 9 3 7
julia> B = rand(1:9, 3, 3) 3x3 Array{Int64,2}: 6 5 5 6 7 5 7 2 7
julia> A \ B 3x3 Array{Float64,2}: 2.01961 0.411765 1.84314 0.254902 1.35294 -0.0392157 -1.70588 -0.823529 -1.35294
and the forward slash (/) right or slash division:
julia> A / B 3x3 Array{Float64,2}: 4.0 -2.0 -1.0 0.285714 0.714286 0.285714 5.07143 -3.07143 -0.428571
With a matrix and a scalar, you can add, subtract, multiply, and divide:
julia> A + 1 3x3 Array{Int64,2}: 6 5 4 9 8 8 10 4 8
julia> [1 2 3 4 5] * 2 1x5 Array{Int64,2}: 2 4 6 8 10
julia> A .- 1 3x3 Array{Int64,2}: 4 3 2 7 6 6 8 2 6
julia> A .* 2 3x3 Array{Int64,2}: 10 8 6 16 14 14 18 6 14
julia> A ./ 2 3x3 Array{Float64,2}: 2.5 2.0 1.5 4.0 3.5 3.5 4.5 1.5 3.5
and more besides:
julia> A // 2 3x4 Array{Rational{Int64},2}: 1//2 2//1 7//2 5//1 1//1 5//2 4//1 11//2 3//2 3//1 9//2 6//1
julia> A .< 6 3x3 BitArray{2}: true true true false false false false true false
You can multiply matrix and a vector (the matrix-vector product), if the arrays have compatible shapes. Here's the matrix A:
θ
and here's a vector V:
θ
The *
operator multiplies them:
θ
The dot or inner product (aTb) can be found using the dot()
function, but you'll have to import the LinearAlgebra library first:
julia> using LinearAlgebra
θ
julia> (1 * 21) + (2 * 22) + (3 * 23) 134
The two arguments must have the same length. You can also use the dot operator, which you can obtain in the REPL by typing "\cdot" followed by a tab:
julia> [1:3] ⋅ [21:23] 134
连接数组和矩阵
[编辑]You can use hcat()
and vcat()
to join matrices together, if their dimensions permit.
hcat()
keeps the first dimension and extends (joins) in the second, vcat()
keeps the second dimension and extends the first.
Here are two 3 by 4 matrices:
julia> A = reshape(1:12, 3, 4) 3x4 Array{Int64,2}: 1 4 7 10 2 5 8 11 3 6 9 12
julia> B = reshape(100:100:1200, 3, 4) 3x4 Array{Int64,2}: 100 400 700 1000 200 500 800 1100 300 600 900 1200
hcat(A, B)
makes a new array that still has 3 rows, but extends/joins the columns to make 8 in total:
julia> hcat(A, B) 3x8 Array{Int64,2}: 1 4 7 10 100 400 700 1000 2 5 8 11 200 500 800 1100 3 6 9 12 300 600 900 1200
vcat(A, B)
makes a new array that keeps the 4 columns, but extends to 6 rows:
julia> vcat(A, B) 6x4 Array{Int64,2}: 1 4 7 10 2 5 8 11 3 6 9 12 100 400 700 1000 200 500 800 1100 300 600 900 1200
You'll probably find the shortcuts useful:
- [A ; B ] is
vcat(A, B)
- [A B ] is
hcat(A, B)
vec()
flattens a matrix into a vector, turning it into a (what some call a 'column') vector:
julia> vec(ones(3, 4)) 12-element Array{Float64,1}: 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
There's also an hvcat()
function ([A B; C D;]
) that does both.
You can use hcat()
to convert an array of arrays to a matrix (using the hcat-splat):
julia> a = Array[[1, 2], [3, 4], [5, 6]] 3-element Array{Array{T,N},1}: [1, 2] [3, 4] [5, 6] julia> hcat(a...) 2x3 Array{Int64,2}: 1 3 5 2 4 6
Julia arrays are 'column-major'. This means that you read down the columns:
1 3 2 4
whereas 'row-major' arrays are to be read across, like this:
1 2 3 4
Column-major order is used in Fortran, R, Matlab, GNU Octave, and by the BLAS and LAPACK engines (the "bread and butter of high-performance numerical computation"). Row-major order is used in C/C++, Mathematica, Pascal, Python, C#/CLI/.Net and others.
增广和扩展数组
[编辑]Often you want to create an array and then add more to it, or 'grow' it. While can do this with vcat()
and hcat()
, be aware that both these operations create new temporary arrays and copy elements, so they don't always produce the fastest code. A better way is to use push!
. This is an efficient operation that extends the array. You can reshape the array later:
julia> a = [] julia> for i = 1:80 push!(a, i) end julia> a 80-element Array{Any,1}: 1 2 3 4 5 6 7 8 9 ⋮ 75 76 77 78 79 80
reshape()
lets you change the dimensions of an array. You can supply the dimensions or use a colon (:
) to ask Julia to calculate valid dimensions:
julia> reshape(a, 10, :) 10x8 Array{Any,2}: 1 11 21 31 41 51 61 71 2 12 22 32 42 52 62 72 3 13 23 33 43 53 63 73 4 14 24 34 44 54 64 74 5 15 25 35 45 55 65 75 6 16 26 36 46 56 66 76 7 17 27 37 47 57 67 77 8 18 28 38 48 58 68 78 9 19 29 39 49 59 69 79 10 20 30 40 50 60 70 80
reshape(a, (10, div(length(a), 10)))
would have the same effect.
push!()
doesn't let you push new rows to a 2D array or matrix. The best way to do the job is to work on a 1D array, as above, adding more elements at the end, and then use reshape()
to convert it to two dimensions. If necessary, use transpose()
to flip the matrix.
操作矩阵
[编辑]To transpose an array or matrix, there's an equivalent '
operator for the transpose()
function, to swap rows and columns:
julia> M = reshape(1:12, 3, 4) 3×4 Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}: 1 4 7 10 2 5 8 11 3 6 9 12
julia> transpose(M) 4x3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 10 11 12
julia> M' 4x3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 10 11 12
To find the determinant of a square matrix, use det()
, after remembering to load the LinearAlgebra library.
julia> using LinearAlgebra julia> A = rand(2:10, 3, 3) 3x3 Array{Int64,2}: 8 8 2 6 9 6 9 2 10
julia> det(A) 438.00000000000006
inv()
(in the Standard Library) finds the inverse of a square matrix, if it has one. (If the determinant of the matrix is zero, it won't have an inverse.)
julia> inv(A) 3x3 Array{Float64,2}: 0.178082 -0.173516 0.0684932 -0.0136986 0.141553 -0.0821918 -0.157534 0.127854 0.0547945
LinearAlgebra.rank()
finds the rank of the matrix, and LinearAlgebra.nullspace()
finds the basis for the nullspace.
julia> A 3x4 Array{Int64,2}: 1 4 7 10 2 5 8 11 3 6 9 12
julia> rank(A) 2
julia> nullspace(A) 4x2 Array{Float64,2}: -0.475185 -0.272395 0.430549 0.717376 0.564458 -0.617566 -0.519821 0.172585
LinearAlgebra.tr()
sums the diagonal of a square matrix (trace):
julia> s = reshape(1:9, 3, 3) 3x3 Array{Int64,2}: 1 4 7 2 5 8 3 6 9
julia> tr(s) 15
对矩阵调用函数
[编辑]There are a number of functions that can be applied to a matrix:
- sum()
adds every element:
julia> A = reshape(1:9, 3, 3) 3×3 Base.ReshapedArray{Int64,2,UnitRange{Int64},Tuple{}}: 1 4 7 2 5 8 3 6 9
julia> sum(A) 45
You can specify a dimension if you want to sum just columns or rows. So to sum columns, specify dimension 1:
julia> sum(A, dims=(1)) 1x3 Array{Int64,2}: 6 15 24
To sum rows, specify dimension 2:
julia> sum(A, dims=(2)) 3x1 Array{Int64,2}: 12 15 18
- mean()
finds the mean of the values in the matrix:
julia> using Statistics; mean(A) 5.0
As with sum()
, you can specify a dimension, so that you can find the mean of columns (use dimension 1) or rows (use dimension 2):
julia> mean(A, dims=(1)) 1x3 Array{Float64,2}: 2.0 5.0 8.0
julia> mean(A, dims=(2)) 3x1 Array{Float64,2}: 4.0 5.0 6.0
- the min(A, B)
and max(A, B)
functions compare two (or more) arrays element by element, returning a new array with the largest (or smallest) values from each:
julia> A = rand(-1:2:1, 3, 3) 3x3 Array{Int64,2}: -1 -1 -1 -1 1 1 1 -1 1
julia> B = rand(-2:4:2, 3, 3) 3x3 Array{Int64,2}: 2 2 2 2 -2 2 2 2 2
prod()
multiplies a matrix's elements together:
julia> A = reshape(collect(BigInt(1):25), 5, 5) 5×5 Array{BigInt,2}: 1 6 11 16 21 2 7 12 17 22 3 8 13 18 23 4 9 14 19 24 5 10 15 20 25 julia> prod(A) 15511210043330985984000000
(Notice the use of BigInt
, products are very large.)
You can specify a dimension if you want to multiply just columns or rows. To multiply the elements of columns together, specify dimension 1; for rows, use dimension 2:
julia> prod(A, 1) 1x5 Array{Int64,2}: 120 30240 360360 1860480 6375600
julia> prod(A, 2) 5x1 Array{Int64,2}: 22176 62832 129168 229824 375000
矩阵范数
[编辑]大多数矩阵的函数在 LinearAlgebra 库中:
julia> using LinearAlgebra
向量范数
[编辑]向量的欧几里得范数, , 在 LinearAlgebra.norm(x)
:
julia> X = [2, 4, -5] 3-element Array{Int64,1}: 2 4 -5 julia> LinearAlgebra.norm(X) # Euclidean norm 6.708203932499369 julia> LinearAlgebra.norm(x, 1) # 1-norm of the vector, the sum of element magnitudes 11.0
如果 X 是一个 '行' 向量:
julia> X = [2 4 -5] 1x3 Array{Int64,2}: 2 4 -5 julia> LinearAlgebra.norm(X) 6.708203932499369 julia> LinearAlgebra.norm(X, 1) 5.0
对于向量 和 向量 之间的欧几里得距离 , is found by norm(x - y)
:
julia> LinearAlgebra.norm([1 2 3] - [2 4 6]) 3.741657386773941 julia> LinearAlgebra.norm([1, 2, 3] - [2, 4, 6]) 3.741657386773941
The angle between two vectors and is :
acos(dot(a,b)/(norm(a)*norm(b)))
矩阵范数
[编辑]Here's the 1-norm of a matrix (the maximum absolute column sum):
julia> B = [5 -4 2 ; -1 2 3; -2 1 0] 3x3 Array{Int64,2}: 5 -4 2 -1 2 3 -2 1 0
julia> LinearAlgebra.norm(B, 1) 8.0
And here's the infinity norm (the maximum absolute row sum):
julia> LinearAlgebra.norm(B, Inf) 11.0
The Euclidean norm()
is the default:
julia> LinearAlgebra.norm([2 3 ; 4 6]), sqrt(2^2 + 3^2 + 4^2 + 6^2) (8.062257748298547,8.06225774829855)
Scaling and rotating matrices
[编辑]- rmul!(A, n)
scales every element of the matrix in place by a scale factor n
:
julia> A = [1 2 3 4 5 6 7 8 9] 3×3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 julia> rmul!(A, 2) 3×3 Array{Int64,2}: 2 4 6 8 10 12 14 16 18
There are rotation and circular-shifting functions too:
julia> A = [1 2 3 4 5 6 7 8 9] 3×3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9
julia> rot180(A) 3×3 Array{Int64,2}: 9 8 7 6 5 4 3 2 1
julia> circshift(A, (1, 1)) 3×3 Array{Int64,2}: 9 7 8 3 1 2 6 4 5
julia> A 3×3 Array{Int64,2}: 1 2 3 4 5 6 7 8 9
reverse()
makes a copy of a matrix reversing rows or columns:
julia> reverse(A, dims=(1)) 3×3 Array{Int64,2}: 7 8 9 4 5 6 1 2 3
julia> reverse(A, dims=(2)) 3×3 Array{Int64,2}: 3 2 1 6 5 4 9 8 7
squeeze()
and reshape()
can be used to change the dimensions of a matrix. For example, this is how you can use squeeze()
to collapse a row vector (1 by 4) into a 4 by 1 array:
julia> a = [1 2 3 4] 1x4 Array{Int64,2}: 1 2 3 4
julia> ndims(a) 2
julia> b = squeeze(a, dims=(1)) 4-element Array{Int64,1}: 1 2 3 4
julia> ndims(b) 1
数组排序
[编辑]Julia has a flexible sort()
function that returns a sorted copy of an array, and a companion sort!()
version that changes the array so that it's sorted.
You can usually use sort()
without options and obtain the results you'd hoped for:
julia> using Random julia> rp = randperm(10) 10-element Array{Int64,1}: 6 4 7 3 10 5 8 1 9 2
julia> sort(rp) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10
You can sort 2D arrays:
julia> a = reshape(rand(1:20, 20), 4, 5) 4x5 Array{Int64,2}: 19 13 4 10 10 6 20 19 18 12 17 7 15 14 9 1 16 8 7 13
julia> sort(a, dims=(1)) # sort each column, dimension 1 4x5 Array{Int64,2}: 1 7 4 7 9 6 13 8 10 10 17 16 15 14 12 19 20 19 18 13
julia> sort(a, dims=(2)) # sort each row, dimension 2 4x5 Array{Int64,2}: 4 10 10 13 19 6 12 18 19 20 7 9 14 15 17 1 7 8 13 16
although there are more powerful alternatives in sortrows()
and sortcolumns()
— see below for details.
The sortperm()
function is similar to sort()
, but it doesn't return a sorted copy of the collection. Instead it returns a list of indices that could be applied to the collection to produce a sorted version:
julia> r = rand(100:110, 10) 10-element Array{Int64,1}: 103 102 110 108 108 108 104 109 106 106
julia> sortperm(r) 10-element Array{Int64,1}: 2 1 7 9 10 4 5 6 8 3
julia> r[sortperm(r)] 10-element Array{Int64,1}: 102 103 104 106 106 108 108 108 109 110
排序和比较
[编辑]If you need more than the default sort()
offers, use the by
and lt
keywords and provide your own functions for processing and comparing elements during the sort.
sort by
[编辑]The by
function processes each element before comparison and provides the 'key' for the sort. A typical example is the task of sorting a list of numbers in string form into numerical order. Here's the list:
julia> r = ["1E10", "150", "25", "3", "1.5", "1E-10", "0.5", ".999"];
If you use the default sort, the numbers appear in the order in which the characters appear in Unicode/ASCII:
julia> sort(r) 8-element Array{ASCIIString,1}: ".999" "0.5" "1.5" "150" "1E-10" "1E10" "25" "3"
with "1E-10" appearing after "0.999".
To sort the numbers by their value, pass the parse()
function (from the Meta package) to by
:
julia> sort(r, by = x -> Meta.parse(x)) 8-element Array{String,1}: "1E-10" "0.5" ".999" "1.5" "3" "25" "150" "1E10"
The strings are sorted 'by' their value. Notice that the by
function you supply produces the numerical sort key, but the original string elements appear in the final result.
Anonymous functions can be useful when sorting arrays. Here's a 10 rows by 2 columns array of tuples:
julia> table = collect(enumerate(rand(1:100, 10))) 10-element Array{(Int64,Int64),1}: (1,86) (2,25) (3,3) (4,97) (5,89) (6,58) (7,27) (8,93) (9,98) (10,12)
You can sort this array by the second element of each tuple, not the first, by supplying an anonymous function to by
that points to the second element of each. The anonymous function says, given an object x
to sort, sort by the second element of x
:
julia> sort(table, by = x -> x[2]) 10-element Array{(Int64,Int64),1}: (3,3) (10,12) (2,25) (7,27) (6,58) (1,86) (5,89) (8,93) (4,97) (9,98)
Sorting by multiple columns
[编辑]You can supply a tuple of "column" identifiers in the by
function, if you want to sort by more than one column.
julia> a = [[2, 2, 2, 1], [1, 1, 1, 8], [2, 1, 2, 2], [1, 2, 2, 5], [2, 1, 1, 4], [1, 1, 2, 7], [1, 2, 1, 6], [2, 2, 1, 3]] ;
julia> sort(a, by = col -> (col[1], col[2], col[3])) 8-element Array{Array{Int64,1},1}: [1,1,1,8] [1,1,2,7] [1,2,1,6] [1,2,2,5] [2,1,1,4] [2,1,2,2] [2,2,1,3] [2,2,2,1]
This sorts the array first by column 1, then by column 2, then by column 3.
Redefining 'less than'
[编辑]By default, sorting uses the built-in isless()
function when comparing elements. In a sorted array, the first element is less than the second.
You can change this behaviour by passing a different function to the lt
keyword. This function should compare two elements and return true if they're sorted, i.e. if the first element is 'less than' the second, using some definition of 'less than'. The sorting process compares pairs of elements repeatedly until every element of the array is in the right place.
For example, suppose you want to sort an array of words according to the number of vowels in each word; i.e. the more vowels a word has, the earlier in the sorted results it occurs. For example, the word "orange" will be considered to be "less than" the word "lemon", because it has more vowels.
First we'll need a function that counts vowels:
vowelcount(string) = count(c -> (c in "aeiou"), lowercase(string))
Now you can pass an anonymous function to sort()
that compares the vowel count of two elements using this function and then returns the element with a higher count in each case:
sentence = split("Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
sort(sentence, lt = (x,y) -> vowelcount(x) > vowelcount(y))
The result is that the word with the most vowels appears first:
19-element Array{SubString{String},1}:
"adipisicing"
"consectetur"
"eiusmod"
"incididunt"
"aliqua."
"labore"
"dolore"
"Lorem"
"ipsum"
"dolor"
"amet,"
"elit,"
"tempor"
"magna"
"sit"
"sed"
"do"
"ut"
"et"
The sort()
function also lets you specify a reverse sort - after the by
and lt
functions (if used) have done their work, a true value passed to rev
reverses the result.
二维数组排序
[编辑]In Julia 1.0, you can sort multidimensional arrays with sortslices()
.
Here's a simple array of nine strings (you can also use numbers, symbols, functions, or anything that can be compared):
julia> table = ["F" "B" "I"; "A" "D" "G"; "H" "C" "E"] 3×3 Array{String,2}: "F" "B" "I" "A" "D" "G" "H" "C" "E"
You supply a number or a tuple to the dims
("dimensions") keyword that indicates what you want to sort. To sort the table so that the first column is sorted, use 1
:
julia> sortslices(table, dims=1) 3×3 Array{String,2}: "A" "D" "G" "F" "B" "I" "H" "C" "E"
Note that sortslices
returns a new array. The first column is in alphabetical order.
Use dims=2
to sort the table so that the first row is sorted:
julia>> sortslices(table, dims=2) 3×3 Array{String,2}: "B" "F" "I" "D" "A" "G" "C" "H" "E"
Now the first row is in alphabetical order.
If you want to sort by something other than the first item, pass a function to by
. So, to sort rows so that the middle column is in alphabetical order, use:
julia> sortslices(table, dims=1, by = x -> x[2]) 3×3 Array{String,2}: "F" "B" "I" "H" "C" "E" "A" "D" "G"
sortslices
has most of the options that you'll find in sort
, and more besides. You can reverse the order with rev
, change the comparator with lt
, and so on.
元组
[编辑]A tuple is an ordered sequence of elements, like an array. A tuple is represented by parentheses and commas, rather than the square brackets used by arrays. Tuples are mostly good for small fixed-length collections — they're used everywhere in Julia, for example, as argument lists and for returning multiple values from functions.
The important difference between arrays and tuples is that tuples are immutable. Other than that, tuples work in much the same way as arrays, and many array functions can be used on tuples too:
julia> t = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) (1,2,3,4,5,6,7,8,9,10)
julia> t (1,2,3,4,5,6,7,8,9,10)
julia> t[6:end] (6,7,8,9,10)
You can have two-dimensional tuples:
julia> t = ((1, 2), (3, 4)) ((1,2),(3,4))
julia> t[1] (1,2)
julia> t[1][2] 2
But you can't change a tuple:
julia> t[1] = 0 LoadError: MethodError: no method matching set index!...
And, because you can't modify tuples, you can't use any of the functions like push!()
that you use with arrays:
julia> a = [1,2,3]; julia> push!(a,4) 4-element Array{Int64,1}: 1 2 3 4
julia> t = (1,2,3); julia> push!(t,4) LoadError: MethodError: no method matching push!
命名的元组
[编辑]A named tuple is like a combination of a tuple and a dictionary. Like a tuple, a named tuple is ordered and immutable, and enclosed in parentheses; like a dictionary, each element has a unique key that can be used to access it.
You can create a named tuple by providing keys and values directly:
julia> shape1 = (corner1 = (1, 1), corner2 = (-1, -1), center = (0, 0)) (corner1 = (1, 1), corner2 = (-1, -1), center = (0, 0))
To access the values, use the familiar dot syntax:
julia> shape1.corner1 (1, 1) julia> shape1.center (0, 0) julia> (shape1.corner1, shape1.corner2) ((1, 1), (-1, -1))
You can access all the values (destructuring) as with ordinary tuples:
julia> c1, c2, centerp = shape1; julia> c1 (1, 1) julia> c2 (-1, -1)
or just some of them:
julia> c1, c2 = shape1; julia> c1 (1, 1)
julia> c2 (-1, -1)
Elements can be the same type, or different types, but the keys will always be variable names.
You can iterate over a named tuple:
julia> for i in shape1 @show i end i = (1, 1) i = (-1, -1) i = (0, 0) julia> for i in shape1 println(first(i)) end 1 -1 0
Another way to create a named tuple is to provide the keys and values in separate tuples:
julia> ks = (:corner1, :corner2) (:corner1, :corner2) julia> vs = ((10, 10), (20, 20)) ((10, 10), (20, 20)) julia> shape2 = NamedTuple{ks}(vs) (corner1 = (10, 10), corner2 = (20, 20)) julia>shape2.corner1 (10, 10) julia> shape2.corner2 (20, 20)
You can combine two named tuples to make a new one:
julia> colors = (top = "red", bottom = "green") (top = "red", bottom = "green") julia> merge(shape2, colors) (corner1 = (10, 10), corner2 = (20, 20), top = "red", bottom = "green")
You can use existing variables for keys:
julia> d = :density; julia> (corner1 = (10, 10), corner2 = (20, 20), d => 0.99)
(corner1 = (10, 10), corner2 = (20, 20), density = 0.99)
Making single value Named Tuples requires a strategically-placed comma:
julia> shape3 = (corner1 = (1, 1),) (corner1 = (1, 1),)
julia> typeof(shape3) NamedTuple{(:corner1,),Tuple{Tuple{Int64,Int64}}}
If you forget it, you'll see this:
julia> (corner1 = (1, 1)) (1, 1) julia> typeof(corner1) Tuple{Int64,Int64}
You can make new named tuples by combining named tuples together.
julia> shape3 = merge(shape2, colors) (corner1 = (10, 10), corner2 = (20, 20), top = "red", bottom = "green")
Use a comma after a single element named tuple:
julia> merge(shape2, (top = "green",)) (corner1 = (10, 10), corner2 = (20, 20), top = "green")
因为没有逗号,元组将被解释为 merge()
的带圆括号的关键字参数。
To iterate over the "keys", use the fieldnames()
and typeof()
functions:
julia> fieldnames(typeof(shape3)) (:corner1, :corner2, :top, :bottom)
so you can do:
julia> for key in fieldnames(typeof(shape3)) @show getindex(shape3, key) end getindex(shape3, key) = (10, 10) getindex(shape3, key) = (20, 20) getindex(shape3, key) = "red" getindex(shape3, key) = "green"
合并两个元组是明智的。例如,如果您有以下命名的元组:
julia> shape3 (corner1 = (10, 10), corner2 = (20, 20), top = "red", bottom = "green")
并且要添加中心点并更改顶部颜色:
julia> merge(shape3, (center = (0, 0), top="green")) (corner1 = (10, 10), corner2 = (20, 20), top = "green", bottom = "green", center = (0, 0))
将插入新值,并更改现有值。
Types
[编辑]
Types
[编辑]这一节是关于类型的,下一节是关于函数和方法的,理想情况下应该同时阅读这两个主题,因为这两个主题是紧密地联系在一起的。
Types of type
[编辑]数据元素有不同的形状和大小,称为类型。
考虑以下数值:浮点数、有理数和整数:
0.5 1//2 1
对我们人类来说,不假思索地将这些数字相加是很容易的,但计算机不能使用简单的加法程序将所有三个值相加,因为它们的类型是不同的。添加有理数的代码必须考虑分子和分母,而添加整数的代码则不必考虑分子和分母。计算机可能必须将这些值中的两个转换为与第三个值相同的类型(通常是将 整数 和 有理数 首先转换为浮点数),然后将三个浮点数相加在一起。
这种转换类型的工作显然需要时间。因此,要编写真正快速的代码,您需要确保不会不断地将值从一种类型转换为另一种类型,从而浪费计算机的时间。当 Julia 编译您的源代码时(每次您第一次对函数求值时都会发生这种情况),您提供的任何类型指示都允许编译器生成更高效的可执行代码。
转换类型的另一个问题是在某些情况下,您将丢失精度:将有理数转换为浮点数可能会丢失一些精度。
Julia 的设计师的官方说法是,类型是可选的。换句话说,如果您不想担心类型(如果您不介意代码的运行速度慢于它可能),那么您可以忽略它们。但是您将在错误消息和文档中遇到它们,因此您最终将不得不处理它们…
折中的方法是编写 top-level 的代码,而不必担心类型,但是,如果您希望加快代码速度,请找出程序花费时间最多的瓶颈,并清理该区域中的类型。
类型系统
[编辑]关于Julia的类型系统有很多需要了解的地方,所以正式文档才是真正的去处。但这里有一个简短的概述。
类型层次
[编辑]在Julia中,类型是按层次结构组织的,具有树状结构。
在树的根中,我们有一个称为 Any
的特殊类型,所有其他类型都直接或间接地连接到它。非正式地说,我们可以说这种 Any
有孩子。它的孩子被称为 Any
的子类。单个孩子 超类 是 Any
。(但是,请注意,类型之间的层次关系是 explicitly declared 显式声明的,而不是 implied by compatible structure 兼容的结构暗示的。)
通过查看 Number 类型,我们可以看到 Julia 类型层次结构的一个很好的示例。
类型 Number
是类型 Any
的一个直接子类。 查看 Number
的超类是谁,我们可以使用 supertype()
函数:
julia> supertype(Number) Any
我们还能尝试找出 Number
的所有子类 (Number 的子辈,因此也就是, Any 的孙辈). 想要找到,我们可以用 subtypes()
函数:
julia> subtypes(Number) 2-element Array{Union{DataType, UnionAll},1}: Complex Real
我们可以观察到,我们有两个 Number
的子类型:复数 Complex
和实数 Real
。对于数学家来说,实数和复数都是数字。作为一般规则,Julia的类型层次结构反映了现实世界的层次结构。
作为另一个例子,如果 美洲豹 Jaguar
和 狮子 Lion
都是 Julia 的类型,很自然的他们的超类会是猫科动物 Feline
:
julia> abstract type Feline end julia> mutable struct Jaguar <: Feline end julia> mutable struct Lion <: Feline end julia> subtypes(Feline) 2-element Array{Any,1}: Jaguar Lion
具体和抽象类型
[编辑]Julia中的每个对象(非正式地说,这意味着在Julia中可以放入变量的所有内容)都有一个类型。但并非所有类型都可以有各自的对象(该类型的实例)。唯一可以有实例的是所谓的具体类型。这些类型不能有任何子类型。可以有子类型(例如Any
, Number
)的类型称为抽象类型。因此,我们不能拥有Number类型的对象,因为它是抽象类型。换句话说,只有类型树的叶子是具体的类型,并且可以实例化。
如果我们不能创建抽象类型的对象,为什么它们有用呢?有了它们,我们可以为它的任何子类型编写泛化的代码。例如,假设我们编写了一个期望类型为 Number
的变量的函数:
#this function gets a number, and returns the same number plus one
function plus_one(n::Number)
return n + 1
end
在本例中,函数需要一个变量 n
. n
的类型必须是 Number
的子类型(直接的或间接的) 。用 :: 语法表示(但是不要担心语法)。这是什么意思?无论n
的类型是Int
(整数) 还是 Float64
(浮点数), 函数 plus_one()
都可以正常工作. 此外, plus_one()
不适用于任何不是 Number
的子类型 的类型 (例如文本字符串、数组)。
我们可以将具体类型分为两类:基元 primitive (或基本) 和 复杂(或复合)。基元类型用来构建代码块,通常硬编码到 Julia 的心脏中,而复合类型将许多其他类型组合在一起,以表示更高级别的数据结构。
您可能会看到以下基本类型:
- 基本整数和浮点数类型(有符号和无符号):
Int8
,UInt8
,Int16
,UInt16
,Int32
,UInt32
,Int64
,UInt64
,Int128
,UInt128
,Float16
,Float32
, andFloat64
- 更高级的数字类型:
BigFloat
,BigInt
- 布尔和角色类型:
Bool
andChar
- 文本字符串类型:
String
复合类型的一个简单示例是 Rational
,用于表示分数。它由两个部分组成,一个分子和一个分母,两个整数(类型为 Int
)。
调查一下类型
[编辑]Julia 提供了两个用于导航类型层次结构的函数:subtypes()
和 supertype()
。
julia> subtypes(Integer) 4-element Array{Union{DataType, UnionAll},1}: BigInt Bool Signed Unsigned julia> supertype(Float64) AbstractFloat
sizeof()
函数告诉您此类型的项占用的字节数:
julia> sizeof(BigFloat) 32 julia> sizeof(Char) 4
如果您想知道某个特定类型可以容纳的数字有多大,以下两个函数非常有用:
julia> typemax(Int64) 9223372036854775807 julia> typemin(Int32) -2147483648
在基本Julia系统中有340多种类型。可以使用以下函数调查类型层次结构:
function showtypetree(T, level=0)
println("\t" ^ level, T)
for t in subtypes(T)
if t != Any
showtypetree(t, level+1)
end
end
end
showtypetree(Number)
它会为不同的数字类型生成如下所示:
julia> showtypetree(Number) Number Complex Real AbstractFloat BigFloat Float16 Float32 Float64 Integer BigInt Bool Signed Int128 Int16 Int32 Int64 Int8 Unsigned UInt128 UInt16 UInt32 UInt64 UInt8 Irrational Rational
例如,这显示了实数 Real
的四个主要子类型:抽象浮点 AbstractFloat
、整数 Integer
、有理数 Rational
和无理数 Irrational
。
指定变量类型
[编辑]我们已经看到,如果您不指定变量类型的话,Julia会尽最大努力计算出您在代码中放入的内容类型,:
julia> collect(1:10) 10-element Array{Int64,1}: 1 2 3 4 5 6 7 8 9 10 julia> collect(1.0:10) 10-element Array{Float64,1}: 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0
我们还看到,您可以为新的空数组指定类型:
julia> fill!(Array{String}(undef, 3), "Julia") 3-element Array{String,1}: "Julia" "Julia" "Julia"
对于变量,可以指定其值必须具有的类型。由于技术原因,您不能在 top-level 中指定类型。在REPL中,您只能在定义的里面这样做。使用 ::
语法,表示“是这个类型”。所以:
function f(x::Int64)
表示函数 f
有一个接受参数 x
的方法,该参数类型预期为 Int64。请参见函数 Functions。
类型稳定性
[编辑]下面的示例说明了变量类型的选择如何影响 Julia 代码的性能。您能找出这两个函数之间的唯一区别吗:
function t1(n)
s = 0
t = 1
for i in 1:n
s += s/i
t = div(t, i)
end
return t
end
function t2(n)
s = 0.0
t = 1
for i in 1:n
s += s/i
t = div(t, i)
end
return t
end
这两个定义几乎是相同的,除了每个定义开头的s的类型。在运行它们几次之后,计时结果是值得注意的:
julia> @time t1(10000000) 0.658128 seconds (60.00 M allocations: 915.520 MiB, 25.01% gc time)
julia> @time t2(10000000) 0.118332 seconds (4 allocations: 160 bytes)
原因是s开始时是一个整数(s=0表示Julia最初将s视为整数),但是在循环中,它被分配用来保存s/i的结果,这是一个浮点值:必须将其从整数转换为浮点以进行匹配。因此该函数不是类型稳定的-Julia编译器无法对其内容进行假设,因此它不能生成纯整数代码或纯浮点代码。因此,它最终生成的代码并不像它所能产生的那样快。
t1()
的表现明显低于 t2()
. 原因是 s
一开始是一个整数。(s = 0
表示 Julia 最初将 s
视为整数), 但是在循环中,它被分配用来保存s/i
的结果,这是一个浮点值:必须将其从整数转换为浮点以进行匹配。因此此该函数不是类型稳定的,Julia编译器无法对其内容进行假设,也就不能生成纯整数代码或纯浮点代码。因此,它最终生成的代码并不像它写的时候所能产生的那样快。
如果要比较这个小差异导致 Julia 编译器生成的额外编译代码的数量,请运行 @code_native t1(100)
和 @code_native t2(100)
命令。
创建类型
[编辑]在Julia中,程序员很容易就能创建新的类型,受益于 native 类型(由Julia的创建者创建的类型)所具有的相同的性能和语言方面的集成。
In Julia, it's very easy for the programmer to create new types, benefiting from the same performance and language-wise integration that the native types (those made by Julia's creators) have.
抽象类型
[编辑]假设我们要创建一个抽象类型。为此,我们使用 Julia 的关键字 abstract
,后跟要创建的类型的名称:
abstract type MyAbstractType end
默认情况下,您创建的类型是 Any
的直接子类型:
julia> supertype(MyAbstractType) Any
您可以使用 <:
运算符来更改它。例如,如果希望新的抽象类型是 Number
的子类型,则可以声明:
abstract type MyAbstractType2 <: Number end
现在我们得到:
julia> supertype(MyAbstractType2) Number
请注意,在同一Julia会话中(在不退出REPL或结束脚本的情况下),不可能重新定义类型。这就是为什么我们必须创建一个名为 MyAbstractType2
的类型。
具体类型和组合
[编辑]现在可以创建新的复合类型了。请使用 struct
或 mutable struct
关键字,它们的语法与声明超类的语法相同。新类型可以包含多个字段,对象在这些字段中存储值。作为示例,让我们定义一个具体的类型,它是 MyAbstractType
的子类型:
mutable struct MyType <: MyAbstractType
foo
bar::Int
end
我们刚刚创建了一个名为 MyType
的复合结构类型,它是 MyAbstractType
的一个子类型,有两个字段:foo
可以是任何类型,bar
可以是 Int
类型。
如何创建 MyType
对象?默认情况下,Julia 会自动创建一个构造函数,该函数返回该类型的对象。该函数具有相同的类型名称,并且该函数的每个参数对应于每个字段。在此示例中,我们可以通过键入以下命令来创建新对象:
julia> x = MyType("Hello World!", 10) MyType("Hello World!", 10)
这将创建一个 MyType
对象,并赋值 Hello World!
到 foo
字段和 10
到 bar
字段。我们可以使用点符号访问 x
的字段:
julia> x.foo "Hello World!" julia> x.bar 10
此外,我们还可以轻松地更改可变结构的字段值:
julia> x.foo = 3.0 3.0 julia> x.foo 3.0
注意,由于我们在创建类型定义时没有指定 foo
的类型,所以可以随时更改它的类型。
而不同指出在于,当我们尝试更改 x.bar
字段的类型(根据 MyType
的定义,我们将其指定为 Int
)时:
julia> x.bar = "Hello World!" LoadError: MethodError: Cannot `convert` an object of type String to an object of type Int64 This may have arisen from a call to the constructor Int64(...), since type constructors fall back to convert methods.
错误消息告诉我们 Julia 无法更改 x.bar
的类型。这确保了类型稳定的代码,并且可以在编程时提供更好的性能。作为性能提示,在定义类型时指定字段的类型通常是很好的做法。
缺省构造函数用于简单的情况,在这种情况下,您键入类似 typename(field1, field2) 之类的内容来生成该类型的新实例。但有时您希望在构造新实例时执行更多操作,例如检查传入的值。为此,您可以使用内部构造函数,即类型定义中的函数。下一节将展示一个实例。
例子:英国货币
[编辑]下面是一个示例,说明如何创建一个可以处理老式英国货币的简单复合类型。在英国看到曙光并引入十进制货币之前,货币体系用了英镑、先令和便士,其中一英镑由20先令组成,一先令由12便士组成。这就是所谓的 £sd 或 LSD 系统(拉丁语的LiBRAE,Solidii,Denarii,因为该系统起源于罗马帝国)。
要定义合适的类型,开始一个新的复合类型声明:
struct LSD
要包含以英镑、先令和便士为单位的价格,此新类型应包含三个字段:英镑、先令和便士:
pounds::Int
shillings::Int
pence::Int
重要的任务是创建 构造函数。它与类型具有相同的名称,并接受三个值作为参数。在检查了几个无效值之后,特殊的 new()
函数将创建一个带有传入值的新对象。请记住,我们仍然在类型定义中:这是一个内部构造函数。
function LSD(a,b,c)
if a < 0 || b < 0 || c < 0
error("no negative numbers")
end
if c > 12 || b > 20
error("too many pence or shillings")
end
new(a, b, c)
end
现在我们完成类型的定义
end
下面是完整的类型定义:
struct LSD
pounds::Int
shillings::Int
pence::Int
function LSD(a, b, c)
if a < 0 || b < 0
error("no negative numbers")
end
if c > 12 || b > 20
error("too many pence or shillings")
end
new(a, b, c)
end
end
现在有可能创造出新的物品来储存老式的英国价格。使用其名称(调用构造函数)创建此类型的新对象:
julia> price1 = LSD(5, 10, 6) LSD(5, 10, 6) julia> price2 = LSD(1, 6, 8) LSD(1, 6, 8)
而且,由于构造函数中添加了简单的检查,所以不能创建糟糕的价格:
julia> price = LSD(1,0,13) ERROR: too many pence or shillings Stacktrace: [1] LSD(::Int64, ::Int64, ::Int64)
如果检查我们创建的某个价格“对象”的字段:
julia> fieldnames(typeof(price1)) 3-element Array{Symbol,1}: :pounds :shillings :pence
您可以看到以下三个字段,它们存储这些值:
julia> price1.pounds 5 julia> price1.shillings 10 julia> price1.pence 6
下一项任务是使此新类型的行为方式与其他 Julia 对象相同。例如,我们无法让两个价格相加:
julia> price1 + price2 ERROR: MethodError: no method matching +(::LSD, ::LSD) Closest candidates are: +(::Any, ::Any, ::Any, ::Any...) at operators.jl:420
而且输出肯定可以得到改善:
julia> price2 LSD(5, 10, 6)
Julia已经有了加法函数(+
),其中包含为许多类型的对象定义的方法。下面的代码添加了另一个可以处理两个LSD对象的方法:
function Base.:+(a::LSD, b::LSD)
newpence = a.pence + b.pence
newshillings = a.shillings + b.shillings
newpounds = a.pounds + b.pounds
subtotal = newpence + newshillings * 12 + newpounds * 240
(pounds, balance) = divrem(subtotal, 240)
(shillings, pence) = divrem(balance, 12)
LSD(pounds, shillings, pence)
end
这个定义教 Julia 如何处理新的 LSD 对象,并向 +
函数添加一个新方法,这一个方法接受两个 LSD 对象,将它们相加,并生成一个新的 LSD 对象。
现在你可以将两个价格相加:
julia> price1 + price2 LSD(6,17,2)
这的确是将 LSD(5,10,6) 和 LSD(1,6,8) 相加的结果
下一个要解决的问题是 LSD 对象的不吸引人的表示。我们通过添加新方法,以完全相同的方式修复了此问题,但这次是添加到 show()
函数中,该函数属于 Base 环境:
function Base.show(io::IO, money::LSD)
print(io, "£$(money.pounds).$(money.shillings)s.$(money.pence)d")
end
在这里,io
是所有 show()
方法当前使用的输出通道。我们添加了一个简单的表达式,用适当的标点符号和分隔符显示字段值。
julia> println(price1 + price2) £6.17s.2d
julia> show(price1 + price2 + LSD(0,19,11) + LSD(19,19,6)) £27.16s.7d
可以添加一个或多个别名,这些别名是特定类型的备用名称。由于 Price 是 LSD 的一种更好的表达方式,我们将创建一个有效的替代方案:
julia> const Price=LSD LSD julia> show(Price(1, 19, 11)) £1.19s.11d
到目前为止,还不错,但这些 LSD 对象还没有完全开发出来。如果要执行减法、乘法和除法运算,则必须为这些函数定义用于处理LSD的其他方法。减法很简单,只要用先令和便士来摆弄就行了,所以我们暂时不谈这个问题,但是乘法呢?价格乘以数字涉及两种类型的对象,一种是 Price / LSD对象,另一种是--嗯,任何正实数都应该是可能的:
function Base.:*(a::LSD, b::Real)
if b < 0
error("Cannot multiply by a negative number")
end
totalpence = b * (a.pence + a.shillings * 12 + a.pounds * 240)
(pounds, balance) = divrem(totalpence, 240)
(shillings, pence) = divrem(balance, 12)
LSD(pounds, shillings, pence)
end
与我们添加到 Base 的 +
函数中的 +
方法一样,Base 的 *
函数的这种新的 *
方法专门定义为将价格乘以数字。第一次尝试的效果出乎意料的好:
julia> price1 * 2 £11.1s.0d julia> price1 * 3 £16.11s.6d julia> price1 * 10 £55.5s.0d julia> price1 * 1.5 £8.5s.9d julia> price3 = Price(0,6,5) £0.6s.5d julia> price3 * 1//7 £0.0s.11d
然而,有些失败是可以预料到的。我们没有考虑到一分钱中真正过时的部分:半分钱和一分钱:
julia> price1 * 0.25 ERROR: InexactError() Stacktrace: [1] convert(::Type{Int64}, ::Float64) at ./float.jl:675 [2] LSD(::Float64, ::Float64, ::Float64) at ./REPL[36]:40 [3] *(::LSD, ::Float64) at ./REPL[55]:10
(答案应该是 1.7欧元7又二分之一欧元。不幸的是,我们的 LSD 类型不允许一分钱的零碎。)
但还有一个更紧迫的问题。此时此刻,你必须先给出价格,然后再乘以乘数;反过来说,结果是失败的:
julia> 2 * price1 ERROR: MethodError: no method matching *(::Int64, ::LSD) Closest candidates are: *(::Any, ::Any, ::Any, ::Any...) at operators.jl:420 *(::Number, ::Bool) at bool.jl:106 ...
这是因为,尽管Julia可以找到匹配的方法 (a::LSD, b::Number)
,但却找不到另一种方法:(a::Number, b::LSD)
。但是添加它是非常容易的:
function Base.:*(a::Number, b::LSD)
b * a
end
它向Base的 *
函数添加了另一个方法。
julia> price1 * 2 £11.1s.0d
julia> 2 * price1 £11.1s.0d
julia> for i in 1:10 println(price1 * i) end £5.10s.6d £11.1s.0d £16.11s.6d £22.2s.0d £27.12s.6d £33.3s.0d £38.13s.6d £44.4s.0d £49.14s.6d £55.5s.0d
现在的价格看起来就像一家19世纪的英国老店。真的!
如果您想查看到目前为止已添加了多少个方法来处理这种旧的英磅类型,请使用 methodswith
函数:
julia> methodswith(LSD)
4-element Array{Method,1}: *(a::LSD, b::Real) at In[20]:4 *(a::Number, b::LSD) at In[34]:2 +(a::LSD, b::LSD) at In[13]:2 show(io::IO, money::LSD) at In[15]:2
到目前为止只有四个……您还可以继续添加方法,使该类型更加有用。这将取决于您对自己或其他人使用它的设想。例如,您可能希望添加除法和模数方法,并对负货币值进行智能操作。
可变结构
[编辑]这种用于持有英国价格的复合类型被定义为不可变类型。创建价格对象后,不能更改这些对象的值:
julia> price1.pence 6 julia> price1.pence=10 ERROR: type LSD is immutable
要基于现有价格创建新价格,您必须执行以下操作:
julia> price2 = Price(price1.pounds, price1.shillings, 10) £5.10s.10d
对于这个特定的示例,这不是一个大问题,但是当您可能希望修改或更新某个类型中的字段的值,而不是创建一个具有正确值的新字段时,会出现许多应用场景。
对于这些情况,您需要创建一个可变结构 mutable struct
。根据对类型的要求,在 struct
和 mutable struct
之间进行选择。
有关模块和从其他模块导入函数的更多信息,请参见 Modules and packages。
Controlling the Flow
[编辑]
控制流程的不同方法
[编辑]通常,Julia程序的每一行都是依次计算的。有多种方法可以控制和修改评估流程。它们与其他语言中使用的结构相对应:
- 三元 复合表达式
- 布尔转换表达式
- if elseif else end - 条件求值
- for end - 迭代求值
- while end - 迭代条件求值
- try catch error throw 异常处理
- do 代码块
三元表达式
[编辑]通常,如果某个条件为真,您会希望执行job A (或调用函数A),如果不是,则希望执行 job B(函数B)。写流程控制最快的方法是使用三元运算符("?" 和 ":"):
julia> x = 1
1
julia> x > 3 ? "yes" : "no"
"no"
julia> x = 5
5
julia> x > 3 ? "yes" : "no"
"yes"
下面是另一个例子:
julia> x = 0.3
0.3
julia> x < 0.5 ? sin(x) : cos(x)
0.29552020666133955
Julia 返回 sin(x)
的值, 因为 x 小于 0.5. cos(x)
根本没有被计算求值。
布尔转换表达式
[编辑]布尔运算符使您可以在条件为真时对表达式求值。可以使用 &&
或 ||
组合条件和表达式。&&
表示“和”,而 ||
表示“或”。由于 Julia 逐个计算表达式,因此仅当前一个条件为 true 或 false 时,才能轻松安排对表达式求值。
下面的示例使用一个 Julia 函数,该函数根据数字是否为奇数返回 true或 false :isodd(n)
。
对于 &&
,这两个部分都必须是 true ,所以我们可以这样写:
julia> isodd(1000003) && @warn("That's odd!")
WARNING: That's odd!
julia> isodd(1000004) && @warn("That's odd!")
false
如果第一个条件(数字为奇数)为真,则计算第二个表达式。如果第一个不为真,则不对表达式求值,只返回条件。
另一方面,使用 ||
运算符:
julia> isodd(1000003) || @warn("That's odd!")
true
julia> isodd(1000004) || @warn("That's odd!")
WARNING: That's odd!
如果第一个条件为 true,则不需要对第二个表达式求值,因为我们已经有“或”所需的一个真值,并且它返回值true。如果第一个条件为false,则对第二个表达式求值,因为该表达式可能被证明为真。
这类求值也称为“短路求值”。
If and Else
[编辑]对于更一般和传统的条件执行方法,您可以使用 if
, elseif
和 else
。如果您习惯使用其他语言,请不要担心空格、大括号、缩进、括号、分号或诸如此类的东西,但请记住使用end完成条件的构造。
name = "Julia"
if name == "Julia"
println("I like Julia")
elseif name == "Python"
println("I like Python.")
println("But I prefer Julia.")
else
println("I don't know what I like")
end
elseif
和 else
的这部分是可选的:
name = "Julia"
if name == "Julia"
println("I like Julia")
end
别忘了 end
!
"switch"和"case"语句呢?emmmm,你不需要学习这些语法,因为他们不存在!
ifelse
[编辑]有一个 ifelse
函数。看起来是这样的:
julia> s = ifelse(false, "hello", "goodbye") * " world"
ifelse
是一个普通函数,它计算所有参数,并根据第一个参数的值返回第二个或第三个参数。使用条件 if
或 ? ... :
,只对所选路由中的表达式求值。或者,也可以这样:
julia> x = 10 10
julia> if x > 0 "positive" else "negative or zero" end "positive"
julia> r = if x > 0 "positive" else "negative or zero" end "positive" julia> r "positive"
for循环 和 迭代
[编辑]遍历一个列表或一组值或从起始值到终止值都是迭代的样例,而 for
... end
构造可以让您遍历许多不同类型的对象,包括范围、数组、集、字典和字符串。
下面是通过一系列值进行简单迭代的标准语法:
julia> for i in 0:10:100 println(i) end 0 10 20 30 40 50 60 70 80 90 100
变量 i
依次获取数组中每个元素的值(它是从 Range 对象构建的):这里以10为步长从0到100。
julia> for color in ["red", "green", "blue"] # an array print(color, " ") end red green blue
julia> for letter in "julia" # a string print(letter, " ") end j u l i a
julia> for element in (1, 2, 4, 8, 16, 32) # a tuple print(element, " ") end 1 2 4 8 16 32
julia> for i in Dict("A"=>1, "B"=>2) # a dictionary println(i) end "B"=>2 "A"=>1
julia> for i in Set(["a", "e", "a", "e", "i", "o", "i", "o", "u"]) println(i) end e o u a i
我们还没有见过集和字典,但是遍历它们是完全一样的。
可以遍历二维数组,从上到下逐步“向下”遍历第1列,然后遍历第2列,依此类推:
julia> a = reshape(1:100, (10, 10)) 10x10 Array{Int64,2}: 1 11 21 31 41 51 61 71 81 91 2 12 22 32 42 52 62 72 82 92 3 13 23 33 43 53 63 73 83 93 4 14 24 34 44 54 64 74 84 94 5 15 25 35 45 55 65 75 85 95 6 16 26 36 46 56 66 76 86 96 7 17 27 37 47 57 67 77 87 97 8 18 28 38 48 58 68 78 88 98 9 19 29 39 49 59 69 79 89 99 10 20 30 40 50 60 70 80 90 100
julia> for n in a print(n, " ") end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
您可以使用 =
代替 in
.
迭代数组并更新
[编辑]当您在数组上迭代时,每次循环中都会检查数组,以防它发生更改。一个你应该避免犯的错误是使用 push!
使数组在循环中增长。仔细运行以下文本,当您看到足够多的内容时准备使用 Ctrl-C
(否则您的计算机最终会崩溃):
julia> c = [1] 1-element Array{Int64,1}: 1 julia> for i in c push!(c, i) @show c sleep(1) end c = [1,1] c = [1,1,1] c = [1,1,1,1] ...
循环变量和作用域
[编辑]遍历每个项目的变量,即“循环变量”,仅存在于循环内部,并在循环结束后立即消失。
julia> for i in 1:10 @show i end i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 7 i = 8 i = 9 i = 10 julia> i ERROR: UndefVarError: i not defined
如果要记住循环之外的循环变量的值(例如,如果必须退出循环并需要知道所达到的值),请使用 global
关键字定义一个比循环更持久的变量。
julia> for i in 1:10 global howfar if i % 4 == 0 howfar = i end end
julia> howfar 8
在这里,howfar
在循环之前是不存在的,但是当循环结束时,它存活了下来,讲述了它的故事。如果 howfar
在循环开始之前已存在,则仅当在循环中使用全局时才能更改其值。
在REPL中工作与在函数中编写代码略有不同。在函数中,您可以这样写:
function f()
howfar = 0
for i in 1:10
if i % 4 == 0
howfar = i
end
end
return howfar
end
@show f()
8
循环中声明的变量
[编辑]类似地,如果您在循环中声明一个新变量,则该变量在循环完成后将不复存在。在此示例中,k 是在以下位置创建的:
julia> for i in 1:5 k = i^2 println("$(i) squared is $(k)") end
1 squared is 1 2 squared is 4 3 squared is 9 4 squared is 16 5 squared is 25
因此它在循环完成后不存在:
julia> k ERROR: UndefVarError: k not defined
在循环的一个迭代中创建的变量在每次迭代结束时都会被忘记。在此循环中:
for i in 1:10
z = i
println("z is $z")
end
z is 1
z is 2
z is 3
z is 4
z is 5
z is 6
z is 7
z is 8
z is 9
z is 10
每次都会重新创建 z
。如果希望变量从一个迭代持续到另一个迭代,它必须是全局的:
julia> counter = 0 0 julia> for i in 1:10 global counter counter += i end julia> counter 55
要更详细地了解这一点,请考虑下面的代码。
for i in 1:10
if ! @isdefined z
println("z isn't defined")
end
z = i
println("z is $z")
end
也许您只期望第一个循环产生“z未定义的错误”?事实上,即使 z
是在循环体中创建的,在下一次迭代开始时也没有定义它。
z isn't defined
z is 1
z isn't defined
z is 2
z isn't defined
z is 3
z isn't defined
z is 4
z isn't defined
z is 5
z isn't defined
z is 6
z isn't defined
z is 7
z isn't defined
z is 8
z isn't defined
z is 9
z isn't defined
z is 10
再说一遍,使用 global
关键字强制 z
在创建后在循环之外可用:
for i in 1:10
global z
if ! @isdefined z
println("z isn't defined")
else
println("z was $z")
end
z = i
println("z is $z")
end
z isn't defined
z is 1
z was 1
z is 2
z was 2
...
z is 9
z was 9
z is 10
如果您在全局范围内工作,虽然 z
现在任何地方都可用,值为10。
although, if you're working in global scope, z
is now available everywhere, with the value 10.
这种行为是因为我们在 REPL 工作。通常更好的做法是将代码放在函数中,在函数中不需要将从循环外部继承的变量标记为全局变量:
function f()
counter = 0
for i in 1:10
counter += i
end
return counter
end
julia> f() 55
循环微调:continue
[编辑]有时,在特定的迭代中,您可能希望跳到下一个值。您可以使用 continue
跳过循环中的其余代码,并使用下一个值再次启动循环。
for i in 1:10
if i % 3 == 0
continue
end
println(i) # this and subsequent lines are
# skipped if i is a multiple of 3
end
1
2
4
5
7
8
10
推导
[编辑]这一概念只是产生和收集项目的一种方式。在数学界,你会这样说:
"设S是所有元素n的集合,其中n大于或等于1,小于或等于10。"
在Julia中,您可以这样写:
julia> S = Set([n for n in 1:10]) Set([7,4,9,10,2,3,5,8,6,1])
[n for n in 1:10]
的结构被称为 数组推导 或 列表推导 ("comprehension",意思是“得到一切”,而不是“理解”)。外面的方括号将通过 计算放置在 for 迭代之前的表达式 而生成的元素集合在一起。而不是end
,使用方括号完成。
julia> [i^2 for i in 1:10] 10-element Array{Int64,1}: 1 4 9 16 25 36 49 64 81 100
可以指定元素的类型:
julia> Complex[i^2 for i in 1:10] 10-element Array{Complex,1}: 1.0+0.0im 4.0+0.0im 9.0+0.0im 16.0+0.0im 25.0+0.0im 36.0+0.0im 49.0+0.0im 64.0+0.0im 81.0+0.0im 100.0+0.0im
但是 Julia 可以计算出你所产生的结果的类型:
julia> [(i, sqrt(i)) for i in 1:10] 10-element Array{Tuple{Int64,Float64},1}: (1,1.0) (2,1.41421) (3,1.73205) (4,2.0) (5,2.23607) (6,2.44949) (7,2.64575) (8,2.82843) (9,3.0) (10,3.16228)
下面是如何通过推导来生成字典:
julia> Dict(string(Char(i + 64)) => i for i in 1:26) Dict{String,Int64} with 26 entries: "Z" => 26 "Q" => 17 "W" => 23 "T" => 20 "C" => 3 "P" => 16 "V" => 22 "L" => 12 "O" => 15 "B" => 2 "M" => 13 "N" => 14 "H" => 8 "A" => 1 "X" => 24 "D" => 4 "G" => 7 "E" => 5 "Y" => 25 "I" => 9 "J" => 10 "S" => 19 "U" => 21 "K" => 11 "R" => 18 "F" => 6
接下来,这里是一个理解中的两个迭代器,用逗号分隔,这使得生成表变得非常容易。在这里,我们将创建一个元组表格:
julia> [(r,c) for r in 1:5, c in 1:2] 5×2 Array{Tuple{Int64,Int64},2}: (1,1) (1,2) (2,1) (2,2) (3,1) (3,2) (4,1) (4,2) (5,1) (5,2)
r
经过五个循环,每个 c
的值对应一个循环。 嵌套循环 的工作方式正好相反。此处将遵守列主次顺序,如阵列中填充了纳秒时间值时所示:
julia> [Int(time_ns()) for r in 1:5, c in 1:2] 5×2 Array{Int64,2}: 1223184391741562 1223184391742642 1223184391741885 1223184391742817 1223184391742067 1223184391743009 1223184391742256 1223184391743184 1223184391742443 1223184391743372
您还可以提供一个测试表达式来过滤产品。例如,生成 1 到 100 之间可被 7 整除的所有整数:
julia> [x for x in 1:100 if x % 7 == 0] 14-element Array{Int64,1}: 7 14 21 28 35 42 49 56 63 70 77 84 91 98
生成表达式
[编辑]与推导类似,生成器表达式可用于通过迭代变量来生成值,但与理解不同的是,这些值是按需生成的。
julia> sum(x^2 for x in 1:10) 385
julia> collect(x for x in 1:100 if x % 7 == 0) 14-element Array{Int64,1}: 7 14 21 28 35 42 49 56 63 70 77 84 91 98
枚举数组
[编辑]通常,您希望逐个元素检查数组元素,同时跟踪每个元素的索引号。enumerate()
函数的作用是:生成一个索引号和每个索引号的值,从而为您提供一个可迭代版本的内容:
julia> m = rand(0:9, 3, 3) 3×3 Array{Int64,2}: 6 5 3 4 0 7 1 7 4 julia> [i for i in enumerate(m)] 3×3 Array{Tuple{Int64,Int64},2}: (1, 6) (4, 5) (7, 3) (2, 4) (5, 0) (8, 7) (3, 1) (6, 7) (9, 4)
在循环的每次迭代中检查数组是否可能发生更改。
数组压缩
[编辑]有时,您希望同时处理两个或更多个数组,先取每个数组的第一个元素,然后再取第二个,依此类推。使用名字挺好的 zip()
函数可以做到这一点:
julia> for i in zip(0:10, 100:110, 200:210) println(i) end
(0,100,200) (1,101,201) (2,102,202) (3,103,203) (4,104,204) (5,105,205) (6,106,206) (7,107,207) (8,108,208) (9,109,209) (10,110,210)
如果数组的大小不同,你会认为它们都会出错。如果第三个数组太大或太小怎么办?
julia> for i in zip(0:10, 100:110, 200:215) println(i) end (0,100,200) (1,101,201) (2,102,202) (3,103,203) (4,104,204) (5,105,205) (6,106,206) (7,107,207) (8,108,208) (9,109,209) (10,110,210)
但 Julia 没有被耍-任何一个数组中的任何一个供过于求或供过于求都会得到很好的处理。
julia> for i in zip(0:15, 100:110, 200:210) println(i) end (0,100,200) (1,101,201) (2,102,202) (3,103,203) (4,104,204) (5,105,205) (6,106,206) (7,107,207) (8,108,208) (9,109,209) (10,110,210)
但是,这在填充数组的情况下不起作用,在这种情况下,维度必须匹配:
(v1.0) julia> [i for i in zip(0:4, 100:102, 200:202)] ERROR: DimensionMismatch("dimensions must match") Stacktrace: [1] promote_shape at ./indices.jl:129 [inlined] [2] axes(::Base.Iterators.Zip{UnitRange{Int64},Base.Iterators.Zip2{UnitRange{Int64},UnitRange{Int64}}}) at ./iterators.jl:371 [3] _array_for at ./array.jl:611 [inlined] [4] collect(::Base.Generator{Base.Iterators.Zip{UnitRange{Int64},Base.Iterators.Zip2{UnitRange{Int64},UnitRange{Int64}}},getfield(Main, Symbol("##5#6"))}) at ./array.jl:624 [5] top-level scope at none:0
(v1.0) julia> [i for i in zip(0:2, 100:102, 200:202)] 3-element Array{Tuple{Int64,Int64,Int64},1}: (0, 100, 200) (1, 101, 201) (2, 102, 202)
可迭代对象
[编辑]对于可以遍历的所有内容,“For Something in Something”结构都是一样的:数组、字典、字符串、集合、范围等等。在Julia中,这是一个普遍的原则:有许多方法可以创建“可迭代对象”,该对象被设计为作为迭代过程的一部分,一次提供一个元素。
我们已经遇到的最明显的例子是Range对象。当您在REPL中键入它时,它看起来并不多:
julia> ro = 0:2:100 0:2:100
但是当你开始遍历它时,它会给出数字:
julia> [i for i in ro] 51-element Array{Int64,1}: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 ⋮ 74 76 78 80 82 84 86 88 90 92 94 96 98 100
如果需要数组中某个范围(或其他可迭代对象)中的数字,可以使用 collect()
将其收集起来:
julia> collect(0:25:100) 5-element Array{Int64,1}: 0 25 50 75 100
您不必收集可迭代对象的每个元素,只需对其进行迭代即可。当您有其他Julia函数创建的可迭代对象时,这一点特别有用。例如,permutations()
创建一个可迭代对象,该对象包含数组的所有排列。当然,您可以使用collect()
获取它们并创建一个新数组:
julia> collect(permutations(1:4))
24-element Array{Array{Int64,1},1}:
[1,2,3,4]
[1,2,4,3]
…
[4,3,2,1]
但是在任何大的东西上,都有成百上千的排列。这就是为什么迭代器对象不能同时从迭代中产生所有值的原因:内存和性能。Range 对象不会占用太多空间,即使遍历它可能需要很长时间,具体取决于范围有多大。如果您一次生成所有的数字,而不是仅在需要时才生成它们,那么它们都必须存储在某个地方,直到您需要使用它们…
Julia为处理其他类型的数据提供了可迭代的对象。例如,在处理文件时,可以将打开的文件视为可迭代对象:
filehandle = "/Users/me/.julia/logs/repl_history.jl"
for line in eachline(filehandle)
println(length(line), line)
end
使用 eachindex()
[编辑]迭代数组时的常见模式是对 i
的每个值执行某些任务,其中 i
是每个元素的索引号,而不是元素:
for i = 1:length(A)
# do something with i or A[i]
end
有一种更有效(即在某些情况下更快)执行此操作的方法:
for i in eachindex(A)
# do something with i or A[i]
end
更多迭代器
[编辑]有一个名为 IterTools.jl 的 Julia 包,它提供了一些高级迭代器函数。
(v1.0) pkg> ] add IterTools julia> using IterTools
例如,partition()
将迭代器中的对象分组到易于处理的块中:
julia> collect(partition(1:10, 3, 1)) 8-element Array{Tuple{Int64,Int64,Int64},1}: (1, 2, 3) (2, 3, 4) (3, 4, 5) (4, 5, 6) (5, 6, 7) (6, 7, 8) (7, 8, 9) (8, 9, 10)
chain()
一个接一个地处理所有迭代器:
for i in chain(1:3, ['a', 'b', 'c'])
@show i
end
i = 1
i = 2
i = 3
i = 'a'
i = 'b'
i = 'c'
subsets()
处理对象的所有子集。可以指定大小:
for i in subsets(collect(1:6), 3)
@show i
end
i = [1,2,3]
i = [1,2,4]
i = [1,2,5]
i = [1,2,6]
i = [1,3,4]
i = [1,3,5]
i = [1,3,6]
i = [1,4,5]
i = [1,4,6]
i = [1,5,6]
i = [2,3,4]
i = [2,3,5]
i = [2,3,6]
i = [2,4,5]
i = [2,4,6]
i = [2,5,6]
i = [3,4,5]
i = [3,4,6]
i = [3,5,6]
i = [4,5,6]
嵌套循环
[编辑]如果希望将一个循环嵌套在另一个循环中,则不必复制 for
和 end
关键字。只要用逗号:
julia> for x in 1:10, y in 1:10
@show (x, y)
end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (1,4)
(x,y) = (1,5)
(x,y) = (1,6)
(x,y) = (1,7)
(x,y) = (1,8)
(x,y) = (1,9)
(x,y) = (1,10)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (2,4)
(x,y) = (2,5)
(x,y) = (2,6)
(x,y) = (2,7)
(x,y) = (2,8)
(x,y) = (2,9)
(x,y) = (2,10)
(x,y) = (3,1)
(x,y) = (3,2)
...
(x,y) = (9,9)
(x,y) = (9,10)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)
(x,y) = (10,4)
(x,y) = (10,5)
(x,y) = (10,6)
(x,y) = (10,7)
(x,y) = (10,8)
(x,y) = (10,9)
(x,y) = (10,10)
(十分有用的@show
宏打印出东西的名称及其值。)
较短和较长形式的嵌套循环之间的一个不同之处是 break
:
julia> for x in 1:10
for y in 1:10
@show (x, y)
if y % 3 == 0
break
end
end
end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
(x,y) = (2,1)
(x,y) = (2,2)
(x,y) = (2,3)
(x,y) = (3,1)
(x,y) = (3,2)
(x,y) = (3,3)
(x,y) = (4,1)
(x,y) = (4,2)
(x,y) = (4,3)
(x,y) = (5,1)
(x,y) = (5,2)
(x,y) = (5,3)
(x,y) = (6,1)
(x,y) = (6,2)
(x,y) = (6,3)
(x,y) = (7,1)
(x,y) = (7,2)
(x,y) = (7,3)
(x,y) = (8,1)
(x,y) = (8,2)
(x,y) = (8,3)
(x,y) = (9,1)
(x,y) = (9,2)
(x,y) = (9,3)
(x,y) = (10,1)
(x,y) = (10,2)
(x,y) = (10,3)
julia> for x in 1:10, y in 1:10
@show (x, y)
if y % 3 == 0
break
end
end
(x,y) = (1,1)
(x,y) = (1,2)
(x,y) = (1,3)
请注意,break
以较短的形式出现在内部循环和外部循环中,而在较长的形式中仅出现在内部循环中。
优化嵌套循环
[编辑]对于Julia,内部循环应该关注行而不是列。这是由于数组如何存储在内存中。例如,在这个Julia数组中,单元格1、2、3和4彼此相邻存储在内存中(“列优先”格式)。因此,从1到2到3向下移动列比沿行移动要快,因为从列到列从1到5到9跳过需要额外的计算:
+-----+-----+-----+--+ | 1 | 5 | 9 | | | | | +--------------------+ | 2 | 6 | 10 | | | | | +--------------------+ | 3 | 7 | 11 | | | | | +--------------------+ | 4 | 8 | 12 | | | | | +-----+-----+-----+--+
下面的示例由简单的循环组成,但是行和列的迭代方式不同。“不好”的版本逐列查看第一行,然后向下移动到下一行,依此类推。
function laplacian_bad(lap_x::Array{Float64,2}, x::Array{Float64,2})
nr, nc = size(x)
for ir = 2:nr-1, ic = 2:nc-1 # bad loop nesting order
lap_x[ir, ic] =
(x[ir+1, ic] + x[ir-1, ic] +
x[ir, ic+1] + x[ir, ic-1]) - 4*x[ir, ic]
end
end
在“好的”版本中,两个循环被正确嵌套,以便内部循环按照数组的内存布局在行中向下移动:
function laplacian_good(lap_x::Array{Float64,2}, x::Array{Float64,2})
nr,nc = size(x)
for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
lap_x[ir,ic] =
(x[ir+1,ic] + x[ir-1,ic] +
x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
end
end
另一种提高速度的方法是使用宏@inbounds
删除数组边界检查:
function laplacian_good_nocheck(lap_x::Array{Float64,2}, x::Array{Float64,2})
nr,nc = size(x)
for ic = 2:nc-1, ir = 2:nr-1 # good loop nesting order
@inbounds begin lap_x[ir,ic] = # no array bounds checking
(x[ir+1,ic] + x[ir-1,ic] +
x[ir,ic+1] + x[ir,ic-1]) - 4*x[ir,ic]
end
end
end
下面是测试函数:
function main_test(nr, nc)
field = zeros(nr, nc)
for ic = 1:nc, ir = 1:nr
if ir == 1 || ic == 1 || ir == nr || ic == nc
field[ir,ic] = 1.0
end
end
lap_field = zeros(size(field))
t = @elapsed laplacian_bad(lap_field, field)
println(rpad("laplacian_bad", 30), t)
t = @elapsed laplacian_good(lap_field, field)
println(rpad("laplacian_good", 30), t)
t = @elapsed laplacian_good_nocheck(lap_field, field)
println(rpad("laplacian_good no check", 30), t)
end
结果表明,仅根据行/列扫描顺序就可以得到不同的性能差异。“不检查”的版本更快.。
julia> main_test(10000,10000) laplacian_bad 1.947936034 laplacian_good 0.190697149 laplacian_good no check 0.092164871
创建自用的可迭代对象
[编辑]可以设计自己的可迭代对象。定义类型时,需要向 Julia 的 iterate()
函数添加几个方法。那你就可以用类似于for
.. end
循环用于处理对象的组件,这些iterate()
方法将在必要时自动调用。
下面的示例演示如何创建一个可迭代对象,该对象生成将大写字母与1到9之间的数字组合在一起的字符串序列。因此,我们的序列中的第一个项目是“A1”,然后是“A2”、“A3”,直到“A9”,然后是“B1”、“B2”,等等,最后是“Z9”。
首先,我们将定义一个名为 SN (StringNumber)的新类型:
mutable struct SN
str::String
num::Int64
end
稍后,我们将使用如下内容创建此类型的可迭代对象:
sn = SN("A", 1)
迭代器将生成到“Z9”的所有字符串。
现在,我们必须向 iterate()
函数添加两个方法。这个函数已经存在于 Julia 中(这就是为什么您可以迭代所有的基本数据对象),需要Base
前缀:我们将向现有的iterate()
函数添加一个新的用于处理这些特殊对象的方法。
第一个方法不接受参数(类型除外),用于启动迭代过程。
function Base.iterate(sn::SN)
str = sn.str
num = sn.num
if num == 9
nextnum = 1
nextstr = string(Char(Int(str[1])) + 1)
else
nextnum = num + 1
nextstr = str
end
return (sn, SN(nextstr, nextnum))
end
这将返回一个元组:我们计算过的迭代器的第一个值和未来的值(以防万一我们希望在“A1”以外的位置启动迭代器)。
iterate()
的第二个方法接受两个参数:可迭代对象和当前状态。它再次返回包含两个值的元组,即下一项和下一状态。但是首先,如果没有更多的值可用,iterate()
函数应该什么也不会返回。
function Base.iterate(sn::SN, state)
# check if we've finished?
if state.str == "[" # when Z changes to [ we're done
return
end
# we haven't finished, so we'll use the incoming one immediately
str = state.str
num = state.num
# and prepare the one after that, to be saved for later
if num == 9
nextnum = 1
nextstr = string(Char(Int(str[1])) + 1)
else
nextnum = num + 1
nextstr = state.str
end
# return: the one to use next, the one after that
return (SN(str, num), SN(nextstr, nextnum))
end
告诉迭代器什么时候完成很简单,因为一旦传入状态包含“[”我们已经完成了,因为“[”(91)的代码紧跟在“Z”(90)的代码之后。
通过添加这两个方法来处理 SN 类型,现在可以对它们进行迭代。为其他几个 Base 函数添加一些方法也很有用,例如show()
和length()
。length()
方法计算出从 sn 开始还有多少 SN 字符串可用。
Base.show(io::IO, sn::SN) = print(io, string(sn.str, sn.num))
function Base.length(sn::SN)
cn1 = Char(Int(Char(sn.str[1]) + 1))
cnz = Char(Int(Char('Z')))
(length(cn1:cnz) * 9) + (10 - sn.num)
end
迭代器现在可以使用了:
julia> sn = SN("A", 1) A1 julia> for i in sn @show i end
i = A1
i = A2
i = A3
i = A4
i = A5
i = A6
i = A7
i = A8
...
i = Z6
i = Z7
i = Z8
i = Z9
julia> for sn in SN("K", 9) print(sn, " ") end
K9 L1 L2 L3 L4 L5 L6 L7 L8 L9 M1 M2 M3 M4 M5 M6 M7 M8 M9 N1 N2 N3 N4 N5 N6 N7 N8
N9 O1 O2 O3 O4 O5 O6 O7 O8 O9 P1 P2 P3 P4 P5 P6 P7 P8 P9 Q1 Q2 Q3 Q4 Q5 Q6 Q7 Q8
Q9 R1 R2 R3 R4 R5 R6 R7 R8 R9 S1 S2 S3 S4 S5 S6 S7 S8 S9 T1 T2 T3 T4 T5 T6 T7 T8
T9 U1 U2 U3 U4 U5 U6 U7 U8 U9 V1 V2 V3 V4 V5 V6 V7 V8 V9 W1 W2 W3 W4 W5 W6 W7 W8
W9 X1 X2 X3 X4 X5 X6 X7 X8 X9 Y1 Y2 Y3 Y4 Y5 Y6 Y7 Y8 Y9 Z1 Z2 Z3 Z4 Z5 Z6 Z7 Z8
Z9
julia> collect(SN("Q", 7)), (Any[Q7, Q8, Q9, R1, R2, R3, R4, R5, R6, R7 … Y9, Z1, Z2, Z3, Z4, Z5, Z6, Z7, Z8, Z9],)
While 循环
[编辑]若要在条件为真时重复某些表达式,请使用while
... end
构造。
julia> x = 0 0 julia> while x < 4 println(x) global x += 1 end 0 1 2 3
如果在函数外部工作,则需要 global
声明 x
,然后才能更改其值。在函数内部,不需要global
。
如果希望在语句之后(而不是在语句之前)测试条件,生成“do...until”的形式,请使用以下结构:
while true
println(x)
x += 1
x >= 4 && break
end
0
1
2
3
这里我们使用的是布尔转换表达式而不是if
... end
语句。
使用Julia的宏,您可以创建自己的控制结构。请参见 Metaprogramming 元编程。
异常
[编辑]如果要编写检查错误并妥善处理这些错误的代码,请使用try
... catch
结构。
使用catch
短语,您可以处理代码中出现的问题,这可能会使程序继续运行,而不是陷入停顿。
在下一个示例中,我们的代码试图直接更改字符串的第一个字符(这是不允许的,因为Julia中的字符串不能就地修改):
julia> s = "string";
julia> try
s[1] = "p"
catch e
println("caught an error: $e")
println("but we can continue with execution...")
end
caught an error: MethodError(setindex!,("string","p",1)) but we can continue with execution...
error()
函数使用给定的消息引发错误异常。
Do block
[编辑]最后,让我们看一看do
代码块,这是另一种语法形式,就像列表推导一样,乍看起来有点倒退(也就是说,从末尾开始,从开头开始或许可以更好地理解它)。
还记得 前面的 find()
示例吗?
julia> smallprimes = [1,2,3,5,7,11,13,17,19,23];
julia> findall(x -> isequal(13, x), smallprimes) 1-element Array{Int64,1}: 7
anonymous function 匿名函数 (x -> isequal(13, x)
是find()
的第一个参数,它对第二个参数进行操作。但是有了do
代码块,你就可以把函数拿出来放在do
之间……
julia> findall(smallprimes) do x isequal(x, 13) end 1-element Array{Int64,1}: 7
您只需丢掉箭头、更改顺序,将find()
函数及其目标参数放在第一位,然后在 do
之后添加匿名函数的参数和主体。
这样做的目的是在表单末尾的多行上编写一个较长的匿名函数,而不是将其作为第一个参数插入。
Functions
[编辑]
函数
[编辑]函数是 Julia 代码的构建块(building blocks),充当其他编程语言中的子例程、过程、块和类似的结构概念。
函数的工作是将一组值作为参数列表并返回值。如果参数包含可变的值(如数组),则可以在函数中修改数组。按照惯例,感叹号(!)函数结尾处的名称表示该函数可以修改其参数。
定义函数有多种语法:
- 函数包含单个表达式
- 函数包含多个表达式
- 函数不需要名字
单表达式函数
[编辑]要定义一个简单的函数,您所需要做的就是在左边提供函数名和参数,在等于号的右边提供一个表达式。这些就像数学函数:
julia> f(x) = x * x
f (generic function with 1 method)
julia> f(2)
4
julia> g(x, y) = sqrt(x^2 + y^2)
g (generic function with 1 method)
julia> g(3,4)
5.0
具有多个表达式的函数
[编辑]用多个表达式定义函数的语法如下:
function functionname(arg1, arg2)
expression
expression
expression
...
expression
end
下面是一个典型的函数,它调用另外两个函数,然后结束。
function breakfast()
maketoast()
brewcoffee()
end
breakfast (generic function with 1 method)
不管最后一个表达式 brewcoffee()
返回的值是什么, breakfast()
函数返回值就是这个。
你可以使用 return
关键字来指定一个特定的值来返回。
julia> function paybills(bankbalance)
if bankbalance < 0
return false
else
return true
end
end
paybills (generic function with 1 method)
julia> payBills(20) true julia> payBills(-10) false
Some consider it good style to always use a return
statement, even if it's not strictly necessary. Later we'll see how to make sure that the function doesn't go adrift if you call it with the wrong type of argument.
多值返回
[编辑]To return more than one value from a function, use a tuple.
function doublesix()
return (6, 6)
end
doublesix (generic function with 1 method)
julia> doublesix() (6, 6)
Here you could write 6, 6
without parentheses.
可选参数和可变数量的参数
[编辑]You can define functions with optional arguments, so that the function can use sensible defaults if specific values aren't supplied. You provide a default symbol and value in the argument list:
function xyzpos(x, y, z=0)
println("$x, $y, $z")
end
xyzpos (generic function with 2 methods)
And when you call this function, if you don't provide a third value, the variable z
defaults to 0 and uses that value inside the function.
julia> xyzpos(1,2) 1, 2, 0 julia> xyzpos(1,2,3) 1, 2, 3
关键字参数与位置参数
[编辑]When you write a function with a long list of arguments like this:
function f(p, q, r, s, t, u)
...
end
早晚你会可能忘记他们的顺序。 sooner or later, you will forget the order in which you have to supply the arguments. For instance, it can be:
f("42", -2.123, atan2, "obliquity", 42, 'x')
or
f(-2.123, 42, 'x', "42", "obliquity", atan2)
You can avoid this problem by using keywords to label arguments. Use a semicolon after the function's unlabelled arguments, and follow it with one or more keyword=value
pairs:
function f(p, q ; r = 4, s = "hello")
println("p is $p")
println("q is $q")
return "r => $r, s => $s"
end
f (generic function with 1 method)
When called, this function expects two arguments, and also accepts a number and a string, labelled r
and s
. If you don't supply the keyword arguments, their default values are used:
julia> f(1,2) p is 1 q is 2 "r => 4, s => hello" julia> f("a", "b", r=pi, s=22//7) p is a q is b "r => π = 3.1415926535897..., s => 22//7"
If you supply a keyword argument, it can be anywhere in the argument list, not just at the end or in the matching place.
julia> f(r=999, 1, 2) p is 1 q is 2 "r => 999, s => hello" julia> f(s="hello world", r=999, 1, 2) p is 1 q is 2 "r => 999, s => hello world" julia>
When defining a function with keyword arguments, remember to insert a semicolon before the keyword/value pairs.
Here's another example from the Julia manual. The rtol
keyword can appear anywhere in the list of arguments or it can be omitted:
julia> isapprox(3.0, 3.01, rtol=0.1) true julia> isapprox(rtol=0.1, 3.0, 3.01) true julia> isapprox(3.0, 3.00001) true
A function definition can combine all the different kinds of arguments. Here's one with normal, optional, and keyword arguments:
function f(a1, opta2=2; key="foo")
println("normal argument: $a1")
println("optional argument: $opta2")
println("keyword argument: $key")
end
f (generic function with 2 methods)
julia> f(1) normal argument: 1 optional argument: 2 keyword argument: foo julia> f(key=3, 1) normal argument: 1 optional argument: 2 keyword argument: 3 julia> f(key=3, 2, 1) normal argument: 2 optional argument: 1 keyword argument: 3
Functions with variable number of arguments
[编辑]Functions can be defined so that they can accept any number of arguments:
function fvar(args...)
println("you supplied $(length(args)) arguments")
for arg in args
println(" argument ", arg)
end
end
The three dots indicate the famous splat. Here it means 'any', including 'none'. You can call this function with any number of arguments:
julia> fvar() you supplied 0 arguments julia> fvar(64) you supplied 1 arguments argument 64 julia> fvar(64,65) you supplied 2 arguments argument 64 argument 65 julia> fvar(64,65,66) you supplied 3 arguments argument 64 argument 65 argument 66
and so on.
Here's another example. Suppose you define a function that accepts two arguments:
function test(x, y)
println("x $x y $y")
end
You can call this in the usual way:
julia> test(12, 34) x 12 y 34
If you have the two numbers, but in a tuple, then how can you supply a single tuple of numbers to this two argument function? Again, the answer is to use the ellipsis (splat).
julia> test((12, 34) ...) x 12 y 34
The use of the ellipsis or 'splat' is also referred to as 'splicing' the arguments:
julia> test([3,4]...) x 3 y 4
You can also do this:
julia> map(test, [3, 4]...) x 3 y 4
局部变量与改变参数的值
[编辑]Any variable you define inside a function will be forgotten when the function finishes.
function test(a,b,c)
subtotal = a + b + c
end
julia> test(1,2,3) 6 julia> subtotal LoadError: UndefVarError: subtotal not defined
If you want to keep values around across function calls, then you can think about using global variables.
A function can't modify an existing variable passed to it as an argument, but it can change the contents of a container passed to it. For example, here is a function that changes its argument to 5:
function set_to_5(x)
x = 5
end
julia> x = 3 3 julia> set_to_5(x) 5 julia> x 3
Although the x
inside the function is changed, the x
outside the function isn't. Variable names in functions are local to the function.
But a function can modify the contents of a container, such as an array. This function uses the [:]
syntax to access the contents of the container x
, rather than change the value of the variable x
:
function fill_with_5(x)
x[:] .= 5
end
julia> x = collect(1:10); julia> fill_with_5(x) 5 julia> x 10-element Array{Int64,1}: 5 5 5 5 5 5 5 5 5 5
You can change elements of the array, but you can't change the variable so that it points to a different array. In other words, your function isn't allowed to change the binding of the argument.
匿名函数
[编辑]Sometimes you don't want to worry about thinking up a cool name for a function. Anonymous functions — functions with no name — can be used in a number of places in Julia, such as with map()
, and in list comprehensions.
The syntax uses ->
, like this:
x -> x^2 + 2x - 1
which defines a nameless function that takes a argument, calls it x
, and returns x^2 + 2x - 1
.
For example, the first argument of the map()
function is a function, and you can define an one-off function that exists just for one particular map()
operation:
julia> map(x -> x^2 + 2x - 1, [1,3,-1]) 3-element Array{Int64,1}: 2 14 -2
After the map()
finishes, both the function and the argument x
have disappeared:
julia> x ERROR: x not defined
If you want an anonymous function that accepts more than one argument, provide the arguments as a tuple:
julia> map((x,y,z) -> x + y + z, [1,2,3], [4, 5, 6], [7, 8, 9]) 3-element Array{Int64,1}: 12 15 18
Notice that the results are 12, 15, 18, rather than 6, 15, and 24. The anonymous function takes the first value of each of the three arrays and adds them, followed by the second, then the third.
In addition, anonymous functions can have zero arguments, if you use an 'empty' tuple()
:
julia> random = () -> rand(0:10) #3 (generic function with 1 method) julia> random() 3
julia> random() 1
Map
[编辑]如果已有一个函数和数组,你可以通过使用 map()
对数组中的每个元素来调用函数。这将会依次对每个元素进行调用并收集结果,返回一个数组。这个过程称之为 映射 (mapping):
julia> a=1:10; julia> map(sin, a) 10-element Array{Float64,1}: 0.841471 0.909297 0.14112 -0.756802 -0.958924 -0.279415 0.656987 0.989358 0.412118 -0.544021
map()
返回一个新的数组,但调用 map!()
则会修改原始数组的内容。
通常,不必用 map()
来对数组的每个成员都调用 sin()
函数,因为很多函数本身就自动支持 "元素级别" 的操作, 两种版本的耗时是相当的( 用 sin.()
可能还有优势,取决于数据规模):
julia> @time map(sin, 1:10000); 0.149156 seconds (568.96 k allocations: 29.084 MiB, 2.01% gc time) julia> @time sin.(1:10000); 0.074661 seconds (258.76 k allocations: 13.086 MiB, 5.86% gc time)
map()
会收集结果然后返回一个数组,优势你只是想要 '映射' 操作但并不需要返回一个数组的结果。这样的话,用 foreach()
:
julia> foreach(println, 1:20) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
此时 ans
是 nothing (ans == nothing
返回 true
).
多个数组映射
[编辑]你可以对不止一个数组进行 map()
操作. The function is applied to the first element of each of the arrays, then to the second, and so on. The arrays must be of the same length (unlike the zip()
function, which is more tolerant).
Here's an example which generates an array of imperial (non-metric) spanner/socket sizes. The second array is just a bunch of repeated 32s to match the integers from 5 to 24 in the first array. Julia simplifies the rationals for us:
julia> map(//, 5:24, fill(32,20)) 20-element Array{Rational{Int64},1}: 5//32 3//16 7//32 1//4 9//32 5//16 11//32 3//8 13//32 7//16 15//32 1//2 17//32 9//16 19//32 5//8 21//32 11//16 23//32 3//4
(In reality, an imperial spanner set won't contain some of these strange sizes - I've never seen an old 17/32" spanner, but you can buy them online.)
使用 . 语法来调用函数
[编辑]除了 map()
以外, it's possible to apply functions directly to arguments that are arrays. See the section on Broadcasting: dot syntax for vectorizing functions.
Reduce 和 folding
[编辑]The map()
function collects the results of some function working on each and every element of an iterable object, such as an array of numbers. The reduce()
function does a similar job, but after every element has been seen and processed by the function, only one is left. The function should take two arguments and return one. The array is reduced by continual application, so that just one is left.
A simple example is the use of reduce()
to sum the numbers in an iterable object (which works like the built-in function sum()
):
julia> reduce(+, 1:10) 55
Internally, this does something similar to this:
((((((((1 + 2) + 3) + 4) + 6) + 7) + 8) + 9) + 10)
After each operation adding two numbers, a single number is carried over to the next iteration. This process reduces all the numbers to a single final result.
A more useful example is when you want to apply a function to work on each consecutive pair in an iterable object. For example, here's a function that compares the length of two strings and returns the longer one:
julia> l(a, b) = length(a) > length(b) ? a : b l (generic function with 1 method)
This can be used to find the longest word in a sentence by working through the string, pair by pair:
julia> reduce(l, split("This is a sentence containing some very long strings")) "containing"
"This" lasts a few rounds, and is then beaten by "sentence", but finally "containing" takes the lead, and there are no other challengers after that. If you want to see the magic happen, redefine l
like this:
julia> l(a, b) = (println("comparing \"$a\" and \"$b\""); length(a) > length(b) ? a : b) l (generic function with 1 method) julia> reduce(l, split("This is a sentence containing some very long strings")) comparing "This" and "is" comparing "This" and "a" comparing "This" and "sentence" comparing "sentence" and "containing" comparing "containing" and "some" comparing "containing" and "very" comparing "containing" and "long" comparing "containing" and "strings" "containing"
You can use an anonymous function to process an array pairwise. The trick is to make the function leave behind a value that will be used for the next iteration. This code takes an array such as [1, 2, 3, 4, 5, 6...]
and returns [1 * 2, 2 * 3, 3 * 4, 4 * 5...]
, multiplying adjacent elements.
store = Int[];
reduce((x,y) -> (push!(store, x * y); y), 1:10)
julia> store 9-element Array{Int64,1}: 2 6 12 20 30 42 56 72 90
Folding
[编辑]Julia also offers two related functions, foldl()
and foldr()
. These offer the same basic functionality as reduce()
. The differences are concerned with the direction in which the traversal occurs. In the simple summation example above, our best guess at what happened inside the reduce()
operation assumed that the first pair of elements were added first, followed by the second pair, and so on. However, it's also possible that reduce()
started at the end and worked towards the front. If it's important, use foldl()
for left to right, and foldr()
for right to left. In many cases, the results are the same, but here's an example where you'll get different results depending on which version you'll use:
julia> reduce(-, 1:10) -53 julia> foldl(-, 1:10) -53 julia> foldr(-, 1:10) -5
Julia offers other functions in this group: check out mapreduce()
, mapfoldl()
, and mapfoldr()
.
If you want to use reduce()
and the fold-()
functions for functions that take only one argument, use a dummy second argument:
julia> reduce((x, y) -> sqrt(x), 1:4, init=256) 1.4142135623730951
which is equivalent to calling the sqrt()
function four times:
julia> sqrt(sqrt(sqrt(sqrt(256)))) 1.4142135623730951
返回函数(柯里化)
[编辑]You can treat Julia functions in the same way as any other Julia object, particularly when it comes to returning them as the result of other functions.
For example, let's create a function-making function. Inside this function, a function called newfunction
is created, and this will raise its argument (y) to the number that was originally passed in as the argument x. This new function is returned as the value of the create_exponent_function()
function.
function create_exponent_function(x)
newfunction = function (y) return y^x end
return newfunction
end
Now we can construct lots of exponent-making functions. First, let's build a squarer()
function:
julia> squarer = create_exponent_function(2) #8 (generic function with 1 method)
and a cuber()
function:
julia> cuber = create_exponent_function(3) #9 (generic function with 1 method)
While we're at it, let's do a "raise to the power of 4" function (called quader
, although I'm starting to struggle with the Latin and Greek naming):
julia> quader = create_exponent_function(4) #10 (generic function with 1 method)
These are ordinary Julia functions:
julia> squarer(4) 16 julia> cuber(5) 125 julia> quader(6) 1296
The definition of the create_exponent_function()
above is perfectly valid Julia code, but it's not idiomatic. For one thing, the return value doesn't always need to be provided explicitly — the final evaluation is returned if return
isn't used. Also, in this case, the full form of the function definition can be replaced with the shorter one-line version. This gives the concise version:
function create_exponent_function(x)
y -> y^x
end
which acts in the same way.
make_counter = function()
so_far = 0
function()
so_far += 1
end
end
julia> a = make_counter(); julia> b = make_counter(); julia> a() 1 julia> a() 2 julia> a() 3 julia> a() 4 julia> b() 1 julia> b() 2
Here's another example of making functions. To make it easier to see what the code is doing, here is the make_counter()
function written in a slightly different manner:
function make_counter()
so_far = 0
counter = function()
so_far += 1
return so_far
end
return counter
end
julia> a = make_counter() #15 (generic function with 1 method) julia> a() 1 julia> a() 2 julia> a() 3 julia> for i in 1:10 a() end julia> a() 14
函数链与组合
[编辑]在 Julia 中函数可以互相组合。
函数组合是指将两个或多个函数应用于参数。你可以使用组合运算符 (∘
) 来组合函数。(在 REPL 中使用\circ
来打出运算符)。举个例子,sqrt()
和 +
函数组合在一起:
julia> (sqrt ∘ +)(3, 5) 2.8284271247461903
先将数字相加,然后计算平方根。
下面这个例子复合了三个函数。
julia> map(first ∘ reverse ∘ uppercase, split("you can compose functions like this")) 6-element Array{Char,1}: 'U' 'N' 'E' 'S' 'E' 'S'
函数链(有时又称为“管道”或者说“用管道来将数据送到下一个函数”)是指将前一个函数的输出应用到后一个函数:
julia> 1:10 |> sum |> sqrt 7.416198487095663
其中 sum()
的值 递给 sqrt()
函数。等价于:
julia> (sqrt ∘ sum)(1:10) 7.416198487095663
管道能够给一个接受单参数的函数发送数据。如果函数需要多个参数,可以用匿名函数:
julia> collect(1:9) |> n -> filter(isodd, n) 5-element Array{Int64,1}: 1 3 5 7 9
Methods
[编辑]A function can have one or more different methods of doing a similar job. Each method usually concentrates on doing the job for a particular type.
Here is a function to check a longitude when you type in a location:
function check_longitude_1(loc)
if -180 < loc < 180
println("longitude $loc is a valid longitude")
else
println("longitude $loc should be between -180 and 180 degrees")
end
end
check_longitude_1 (generic function with 1 method)
The message ("generic function with 1 method") you see if you define this in the REPL tells you that there is currently one way you can call the check_longitude_1()
function. If you call this function and supply a number, it works fine.
julia> check_longitude_1(-182) longitude -182 should be between -180 and 180 degrees julia> check_longitude_1(22) longitude 22 is a valid longitude
But what happens when you type in a longitude in, say, the format seen on Google Maps:
julia> check_longitude_1("1°24'54.6\"W") ERROR: MethodError: `isless` has no method matching isless(::Int64, ::UTF8String)
The error tells us that the function has stopped because the concept of less than (<
), which we are using inside our function, makes no sense if one argument is a string and the other a number. Strings are not less than or greater than integers because they are two different things, so the function fails at that point.
Notice that the check_longitude_1()
function did start executing, though. The argument loc
could have been anything - a string, a floating point number, an integer, a symbol, or even an array. There are many ways for this function to fail. This is not the best way to write code!
To fix this problem, we might be tempted to add code that tests the incoming value, so that strings are handled differently. But Julia proposes a better alternative: methods and multiple dispatch.
In the case where the longitude is supplied as a numeric value, the loc
argument is defined as 'being of type Real'. Let's start again, define a new function, and do it properly:
function check_longitude(loc::Real)
if -180 < loc < 180
println("longitude $loc is a valid longitude")
else
println("longitude $loc should be between -180 and 180 degrees")
end
end
Now this check_longitude
function doesn't even run if the value in loc
isn't a real number. The problems of what to do when the value is a string is avoided. With a type Real, this particular method can be called with any argument provided that it is some kind of number.
We can use the applicable()
function to test this. applicable()
lets you know whether you can apply a function to an argument — i.e. whether there is an available method for the function for arguments with that type:
julia> applicable(check_longitude, -30) true julia> applicable(check_longitude, pi) true julia> applicable(check_longitude, 22/7) true julia> applicable(check_longitude, 22//7) true julia> applicable(check_longitude, "1°24'54.6\"W") false
The false
indicates that you can't pass a string value to the check_longitude()
function because there is no method for this function that accepts a string:
julia> check_longitude("1°24'54.6\"W") ERROR: MethodError: `check_longitude` has no method matching check_longitude(::UTF8String)
Now the body of the function isn't even looked at — Julia doesn't know a method for calling check_longitude()
function with a string argument.
The obvious next step is to add another method for the check_longitude()
function, only this time one that accepts a string argument. In this way, a function can be given a number of alternative methods: one for numeric arguments, one for string arguments, and so on. Julia selects and runs one of the available methods depending on the types of arguments you provide to a function.
This is multiple dispatch.
function check_longitude(loc::String)
# not real code, obviously!
if endswith(loc, "W")
println("longitude $loc is West of Greenwich")
else
println("longitude $loc is East of Greenwich")
end
end
check_longitude (generic function with 2 methods)
Now the check_longitude()
function has two methods. The code to run depends on the types of the arguments you provide to the function. And you can avoid testing the types of arguments at the start of this function, because Julia only dispatches the flow to the string-handling method if loc
is a string.
You can use the built-in methods()
function to find out how many methods you have defined for a particular function.
julia> methods(check_longitude) # 2 methods for generic function "check_longitude": check_longitude(loc::Real) at none:2 check_longitude(loc::String) at none:3
An instructive example is to see how many different methods the +
function has:
julia> methods(+) # 176 methods for generic function "+": [1] +(x::Bool, z::Complex{Bool}) in Base at complex.jl:276 [2] +(x::Bool, y::Bool) in Base at bool.jl:104 ... [174] +(J::LinearAlgebra.UniformScaling, B::BitArray{2}) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v0.7/LinearAlgebra/src/uniformscaling.jl:90 [175] +(J::LinearAlgebra.UniformScaling, A::AbstractArray{T,2} where T) in LinearAlgebra at /Users/osx/buildbot/slave/package_osx64/build/usr/share/julia/stdlib/v0.7/LinearAlgebra/src/uniformscaling.jl:91 [176] +(a, b, c, xs...) in Base at operators.jl:466
This is a long list of every method currently defined for the +
function; there are many different types of thing that you can add together, including arrays, matrices, and dates. If you design your own types, you might well want to write a function that adds two of them together.
Julia chooses "the most specific method" to handle the types of arguments. In the case of check_longitude()
, we have two specific methods, but we could define a more general method:
function check_longitude(loc::Any)
println("longitude $loc should be a string or a number")
end
check_longitude (generic function with 3 methods)
This method of check_longitude()
is called when the loc
argument is neither a Real number or a String. It is the most general method, and won't be called at all if a more specific method is available.
方法定义中的类型参数
[编辑]在方法定义中可以用到类型信息。以此为例:
julia>function test(a::T) where T <: Real
println("$a is a $T")
end
test (generic function with 1 methods)
julia> test(2.3) 2.3 is a Float64 julia> test(2) 2 is a Int64 julia> test(.02) 0.02 is a Float64 julia> test(pi) π = 3.1415926535897... is a Irrational{:π}
julia> test(22//7) 22//7 is a Rational{Int64}
julia> test(0xff) 255 is a UInt8
The test()
method automatically extracts the type of the single argument a
passed to it and stores it in the 'variable' T
. For this function, the definition of T
was where T is a subtype of Real, so the type of T must be a subtype of the Real type (it can be any real number, but not a complex number). 'T' can be used like any other variable — in this method it's just printed out using string interpolation. (It doesn't have to be T
, but it nearly always is!)
This mechanism is useful when you want to constrain the arguments of a particular method definition to be of a particular type. For example, the type of argument a
must belong to the Real number supertype, so this test()
method doesn't apply when a
isn't a number, because then the type of the argument isn't a subtype of Real:
julia> test("str") ERROR: MethodError: no method matching test(::ASCIIString) julia> test(1:3) ERROR: MethodError: no method matching test(::UnitRange{Int64})
Here's an example where you might want to write a method definition that applies to all one-dimensional integer arrays. It finds all the odd numbers in an array:
function findodds(a::Array{T,1}) where T <: Integer
filter(isodd, a)
end
findodds (generic function with 1 method)
julia> findodds(collect(1:20)) 10-element Array{Int64,1}: 1 3 5 7 9 11 13 15 17 19
but can't be used for arrays of real numbers:
julia> findodds([1, 2, 3, 4, 5, 6, 7, 8, 9, 10.0]) ERROR: MethodError: no method matching findodds(::Array{Float64,1}) Closest candidates are: findodds(::Array{T<:Integer,1}) where T<:Integer at REPL[13]:2
Note that, in this simple example, because you're not using the type information inside the method definition, you might be better off sticking to the simpler way of defining methods, by adding type information to the arguments:
function findodds(a::Array{Int64,1})
findall(isodd, a)
end
But if you wanted to do things inside the method that depended on the types of the arguments, then the type parameters approach will be useful.
Dictionaries and Sets
[编辑]
字典 Dict
[编辑]到目前为止,介绍的许多函数都是在数组(和元组)上工作的。数组只是集合的一种类型,Julia 还有其他的集合类型。
简单的 查找表 是组织多种类型数据的有用方法:给定单个信息 (例如称为 键 的数字、字符串或符号),对应的数据 值 是什么?为此,Julia提供了 Dictionary 对象,简称为 Dict。它是一个“关联集合”,因为它将键与值相关联。
创建字典
[编辑]可以使用以下语法创建简单字典:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3) Dict{String,Int64} with 3 entries: "c" => 3 "b" => 2 "a" => 1
dict
现在是字典了。键是“a”、“b”和“c”,对应的值是1、2和3。操作符 =>
称为 Pair
函数。在字典中,键总是唯一的 - 不能有两个同名的键。
如果预先知道键和值的类型,则可以(而且很可能应该) 在 Dict
关键字后用大括号指定它们:
julia> dict = Dict{String,Integer}("a"=>1, "b" => 2) Dict{String,Integer} with 2 entries: "b" => 2 "a" => 1
还可以使用 推导 complementsions 语法创建词典:
julia> dict = Dict(string(i) => sind(i) for i = 0:5:360) Dict{String,Float64} with 73 entries: "320" => -0.642788 "65" => 0.906308 "155" => 0.422618 ⋮ => ⋮
使用以下语法创建类型化的空字典:
julia> dict = Dict{String,Int64}() Dict{String,Int64} with 0 entries
or you can omit the types, and get an untyped dictionary:
julia> dict = Dict() Dict{Any,Any} with 0 entries
It's sometimes useful to create dictionary entries using a for
loop:
files = ["a.txt", "b.txt", "c.txt"]
fvars = Dict()
for (n, f) in enumerate(files)
fvars["x_$(n)"] = f
end
This is one way you could create a set of 'variables' stored in a dictionary:
julia> fvars Dict{Any,Any} with 3 entries: "x_1" => "a.txt" "x_2" => "b.txt" "x_3" => "c.txt"
查看内容
[编辑]To get a value, if you have the key:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5) julia> dict["a"] 1
if the keys are strings. Or, if the keys are symbols:
julia> symdict = Dict(:x => 1, :y => 3, :z => 6) Dict{Symbol,Int64} with 3 entries: :z => 6 :x => 1 :y => 3
julia> symdict[:x] 1
Or if the keys are integers:
julia> intdict = Dict(1 => "one", 2 => "two", 3 => "three") Dict{Int64,String} with 3 entries: 2 => "two" 3 => "three" 1 => "one"
julia> intdict[2] "two"
You can instead use the get()
function, and provide a fail-safe default value if there's no value for that particular key:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)
julia> get(dict, "a", 0) 1 julia> get(dict, "Z", 0) 0
If you don't use a default value as a safety precaution like this, you'll get an error if there's no key:
julia> get(dict, "Z") ERROR: MethodError: no method matching get(::Dict{String,Int64}, ::String) Closest candidates are: get(::Dict{K,V}, ::Any, ::Any) where {K, V} at dict.jl:508 get(::Base.EnvDict, ::AbstractString, ::Any) at env.jl:77
If you don't want get()
to provide a default value, use a try
...catch
block:
try
dict["Z"]
catch error
if isa(error, KeyError)
println("sorry, I couldn't find anything")
end
end
sorry, I couldn't find anything
To change a value assigned to an existing key (or assign a value to a hitherto unseen key):
julia> dict["a"] = 10 10
Keys
[编辑]Keys must be unique for a dictionary. There's always only one key called a
in this dictionary, so when you assign a value to a key that already exists, you're not creating a new one, just modifying an existing one.
To see if the dictionary contains a key, use haskey()
:
julia> haskey(dict, "Z") false
To check for the existence of a key/value pair:
julia> in(("b" => 2), dict) true
To add a new key and value to a dictionary, use this:
julia> dict["d"] = 4 4
You can delete a key from the dictionary, using delete!()
:
julia> delete!(dict, "d") Dict{String,Int64} with 4 entries: "c" => 3 "e" => 5 "b" => 2 "a" => 1
You'll notice that the dictionary doesn't seem to be sorted in any way — at least, the keys are in no particular order. This is due to the way they're stored, and you can't sort them in place. (But see Sorting, below.)
To get all keys, use the keys()
function:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5); julia> keys(dict) Base.KeySet for a Dict{String,Int64} with 5 entries. Keys: "c" "e" "b" "a" "d"
The result is an iterator that has just one job: to iterate through a dictionary key by key:
julia> collect(keys(dict)) 5-element Array{String,1}: "c" "e" "b" "a" "d" julia> [uppercase(key) for key in keys(dict)] 5-element Array{Any,1}: "C" "E" "B" "A" "D"
This uses the list comprehension form ([ new-element for loop-variable in iterator ]
) and each new element is collected into an array. An alternative would be:
julia> map(uppercase, collect(keys(dict))) 5-element Array{String,1}: "C" "E" "B" "A" "D"
Values
[编辑]To retrieve all the values, use the values()
function:
julia> values(dict) Base.ValueIterator for a Dict{String,Int64} with 5 entries. Values: 3 5 2 1 4
If you want to go through a dictionary and process each key/value, you can make use the fact that dictionaries themselves are iterable objects:
julia> for kv in dict println(kv) end "c"=>3 "e"=>5 "b"=>2 "a"=>1 "d"=>4
where kv
is a tuple containing each key/value pair in turn.
Or you could do:
julia> for k in keys(dict) println(k, " ==> ", dict[k]) end c ==> 3 e ==> 5 b ==> 2 a ==> 1 d ==> 4
Even better, you can use a key/value tuple to simplify the iteration even more:
julia> for (key, value) in dict println(key, " ==> ", value) end c ==> 3 e ==> 5 b ==> 2 a ==> 1 d ==> 4
Here's another example:
for tuple in Dict("1"=>"Hydrogen", "2"=>"Helium", "3"=>"Lithium")
println("Element $(tuple[1]) is $(tuple[2])")
end
Element 1 is Hydrogen
Element 2 is Helium
Element 3 is Lithium
(Notice the string interpolation operator, $
. This allows you to use a variable's name in a string and get the variable's value when the string is printed. You can include any Julia expression in a string using $()
.)
字典排序
[编辑]Because dictionaries don't store the keys in any particular order, you might want to output the dictionary to a sorted array to obtain the items in order:
julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6) Dict{String,Int64} with 6 entries: "f" => 6 "c" => 3 "e" => 5 "b" => 2 "a" => 1 "d" => 4
julia> for key in sort(collect(keys(dict))) println("$key => $(dict[key])") end a => 1 b => 2 c => 3 d => 4 e => 5 f => 6
If you really need to have a dictionary that remains sorted all the time, you can use the SortedDict data type from the DataStructures.jl package (after having installed it).
julia> import DataStructures julia> dict = DataStructures.SortedDict("b" => 2, "c" => 3, "d" => 4, "e" => 5, "f" => 6) DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 5 entries: "b" => 2 "c" => 3 "d" => 4 "e" => 5 "f" => 6
julia> dict["a"] = 1 1
julia> dict DataStructures.SortedDict{String,Int64,Base.Order.ForwardOrdering} with 6 entries: "a" => 1 "b" => 2 "c" => 3 "d" => 4 "e" => 5 "f" => 6
例子:统计单词
[编辑]A simple application of a dictionary is to count how many times each word appears in a piece of text. Each word is a key, and the value of the key is the number of times that word appears in the text.
Let's count the words in the Sherlock Holmes stories. I've downloaded the text from the excellent Project Gutenberg and stored them in a file "sherlock-holmes-canon.txt". To create a list of words from the loaded text in canon
, we'll split the text using a regular expression, and convert every word to lowercase. (There are probably faster methods.)
julia> f = open("sherlock-holmes-canon.txt") julia> wordlist = String[] julia> for line in eachline(f) words = split(line, r"\W") map(w -> push!(wordlist, lowercase(w)), words) end julia> filter!(!isempty, wordlist) julia> close(f)
wordlist
is now an array of nearly 700,000 words:
julia> wordlist[1:20] 20-element Array{String,1}: "THE" "COMPLETE" "SHERLOCK" "HOLMES" "Arthur" "Conan" "Doyle" "Table" "of" "contents" "A" "Study" "In" "Scarlet" "The" "Sign" "of" "the" "Four" "The"
To store the words and the word counts, we'll create a dictionary:
julia> wordcounts = Dict{String,Int64}() Dict{String,Int64} with 0 entries
To build the dictionary, loop through the list of words, and use get()
to look up the current tally, if any. If the word has already been seen, the count can be increased. If the word hasn't been seen before, the fall-back third argument of get()
ensures that the absence doesn't cause an error, and 1 is stored instead.
for word in wordlist
wordcounts[word]=get(wordcounts, word, 0) + 1
end
Now you can look up words in the wordcounts
dictionary and find out how many times they appear:
julia> wordcounts["watson"] 1040 julia> wordcounts["holmes"] 3057 julia> wordcounts["sherlock"] 415 julia> wordcounts["lestrade"] 244
Dictionaries aren't sorted, but you can use the collect()
and keys()
functions on the dictionary to collect the keys and then sort them. In a loop you can work through the dictionary in alphabetical order:
for i in sort(collect(keys(wordcounts)))
println("$i, $(wordcounts[i])")
end
000, 5
1, 8
10, 7
100, 4
1000, 9
104, 1
109, 1
10s, 2
10th, 1
11, 9
1100, 1
117, 2
117th, 2
11th, 1
12, 2
120, 2
126b, 3
⋮
zamba, 2
zeal, 5
zealand, 3
zealous, 3
zenith, 1
zeppelin, 1
zero, 2
zest, 3
zig, 1
zigzag, 3
zigzagged, 1
zinc, 3
zion, 2
zoo, 1
zoology, 2
zu, 1
zum, 2
â, 41
ã, 4
But how do you find out the most common words? One way is to use collect()
to convert the dictionary to an array of tuples, and then to sort the array by looking at the last value of each tuple:
julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true) 19171-element Array{Pair{String,Int64},1}: ("the",36244) ("and",17593) ("i",17357) ("of",16779) ("to",16041) ("a",15848) ("that",11506) ⋮ ("enrage",1) ("smuggled",1) ("lounges",1) ("devotes",1) ("reverberated",1) ("munitions",1) ("graybeard",1)
To see only the top 20 words:
julia> sort(collect(wordcounts), by = tuple -> last(tuple), rev=true)[1:20] 20-element Array{Pair{String,Int64},1}: ("the",36244) ("and",17593) ("i",17357) ("of",16779) ("to",16041) ("a",15848) ("that",11506) ("it",11101) ("in",10766) ("he",10366) ("was",9844) ("you",9688) ("his",7836) ("is",6650) ("had",6057) ("have",5532) ("my",5293) ("with",5256) ("as",4755) ("for",4713)
In a similar way, you can use the filter()
function to find, for example, all words that start with "k" and occur less than four times:
julia> filter(tuple -> startswith(first(tuple), "k") && last(tuple) < 4, collect(wordcounts)) 73-element Array{Pair{String,Int64},1}: ("keg",1) ("klux",2) ("knifing",1) ("keening",1) ("kansas",3) ⋮ ("kaiser",1) ("kidnap",2) ("keswick",1) ("kings",2) ("kratides",3) ("ken",2) ("kindliness",2) ("klan",2) ("keepsake",1) ("kindled",2) ("kit",2) ("kicking",1) ("kramm",2) ("knob",1)
更加复杂的结构
[编辑]A dictionary can hold many different types of values. Here for example is a dictionary where the keys are strings and the values are arrays of arrays of points (assuming that the Point type has been defined already). For example, this could be used to store graphical shapes describing the letters of the alphabet (some of which have two or more loops):
julia> p = Dict{String, Array{Array}}() Dict{String,Array{Array{T,N},N}} julia> p["a"] = Array[[Point(0,0), Point(1,1)], [Point(34, 23), Point(5,6)]] 2-element Array{Array{T,N},1}: [Point(0.0,0.0), Point(1.0,1.0)] [Point(34.0,23.0), Point(5.0,6.0)] julia> push!(p["a"], [Point(34.0,23.0), Point(5.0,6.0)]) 3-element Array{Array{T,N},1}: [Point(0.0,0.0), Point(1.0,1.0)] [Point(34.0,23.0), Point(5.0,6.0)] [Point(34.0,23.0), Point(5.0,6.0)]
Or create a dictionary with some already-known values:
julia> d = Dict("shape1" => Array [ [ Point(0,0), Point(-20,57)], [Point(34, -23), Point(-10,12) ] ]) Dict{String,Array{Array{T,N},1}} with 1 entry: "shape1" => Array [ [ Point(0.0,0.0), Point(-20.0,57.0)], [Point(34.0,-23.0), Point(-10.0,12.0) ] ]
Add another array to the first one:
julia> push!(d["shape1"], [Point(-124.0, 37.0), Point(25.0,32.0)]) 3-element Array{Array{T,N},1}: [Point(0.0,0.0), Point(-20.0,57.0)] [Point(34.0,-23.0), Point(-10.0,12.0)] [Point(-124.0,37.0), Point(25.0,32.0)]
集合 Set
[编辑]Set 是元素的集合,就像是一个没有重复元素的字典或数组。
Set 和其他类型的集合有两个不同之处:
- Set 中每个元素只能有一份
- 元素的顺序不重要
(而数组对同一个元素可以有多份,并且是有序的)
你可以通过使用 Set
构造函数创建一个空的集合:
julia> colors = Set() Set{Any}({})
和 Julia 的其他地方一样,你可以指定类型:
julia> primes = Set{Int64}() Set(Int64)[]
可以一次操作创建和填充 Set:
julia> colors = Set{String}(["red","green","blue","yellow"]) Set(String["yellow","blue","green","red"])
或者你可以让 Julia “猜出类型”:
julia> colors = Set(["red","green","blue","yellow"]) Set{String}({"yellow","blue","green","red"})
相当一部分处理数组的函数也可以用于处理集合。例如,将元素添加到集合 类似于 将元素添加到数组。您可以使用 push!()
:
julia> push!(colors, "black") Set{String}({"yellow","blue","green","black","red"})
But you can't use pushfirst!()
, because that works only for things that have a concept of "first", like arrays.
What happens if you try to add something to the set that's already there? Absolutely nothing. You don't get a copy added, because it's a set, not an array, and sets don't store repeated elements.
To see if something is in the set, you can use in()
:
julia> in("green", colors) true
There are some standard operations you can do with sets, namely find their union, intersection, and difference, with the functions, union()
, intersect()
, and setdiff()
:
julia> rainbow = Set(["red","orange","yellow","green","blue","indigo","violet"]) Set(String["indigo","yellow","orange","blue","violet","green","red"])
The union of two sets is the set of everything that is in one or the other sets. The result is another set – so you can't have two "yellow"s here, even though we've got a "yellow" in each set:
julia> union(colors, rainbow) Set(String["indigo","yellow","orange","blue","violet","green","black","red"])
The intersection of two sets is the set that contains every element that belongs to both sets:
julia> intersect(colors, rainbow) Set(String["yellow","blue","green","red"])
The difference between two sets is the set of elements that are in the first set, but not in the second. This time, the order in which you supply the sets matters. The setdiff()
function finds the elements that are in the first set, colors
, but not in the second set, rainbow
:
julia> setdiff(colors, rainbow) Set(String["black"])
其他函数
[编辑]处理数组和集合的函数有时也适用于字典和其他集合。例如,某些 集合的操作可应用于词典,而不仅仅是 Set 和 数组:
julia> d1 = Dict(1=>"a", 2 => "b") Dict{Int64,String} with 2 entries: 2 => "b" 1 => "a" julia> d2 = Dict(2 => "b", 3 =>"c", 4 => "d") Dict{Int64,String} with 3 entries: 4 => "d" 2 => "b" 3 => "c" julia> union(d1, d2) 4-element Array{Pair{Int64,String},1}: 2=>"b" 1=>"a" 4=>"d" 3=>"c" julia> intersect(d1, d2) 1-element Array{Pair{Int64,String},1}: 2=>"b" julia> setdiff(d1, d2) 1-element Array{Pair{Int64,String},1}: 1=>"a"
请注意,结果是以对数组的形式返回的,而不是以字典的形式返回的。
filter()
, map()
和 collect()
等函数 (我们已经看到它们用于数组) 也适用于字典:
julia> filter((k, v) -> k == 1, d1) Dict{Int64,String} with 1 entry: 1 => "a"
有一个 merge()
函数,它可以合并两个字典:
julia> merge(d1, d2) Dict{Int64,String} with 4 entries: 4 => "d" 2 => "b" 3 => "c" 1 => "a"
findmin()
函数可以在字典中找到最小值,然后返回值及其键。
julia> d1 = Dict(:a => 1, :b => 2, :c => 0) Dict{Symbol,Int64} with 3 entries: :a => 1 :b => 2 :c => 0 julia> findmin(d1) (0, :c)
Strings and Characters
[编辑]
Strings and characters
[编辑]Strings
[编辑]字符串是由一个或多个字符组成的序列,通常用双引号括起来:
"this is a string"
关于字符串,有两件重要的事情需要了解。
一个是,它们是不可变的。一旦它们被创建,你就不能改变它们。但是,从现有字符串的某些部分创建新字符串是很容易的。
第二点是,在使用两个特定字符时必须小心:双引号(")和美元符号($)。如果要在字符串中包含双引号字符,则必须在其前面加上反斜杠,否则字符串的其余部分将被解释为 Julia 代码,可能会产生有趣的结果。如果您想在字符串中包含一个美元符号($),也应该以反斜杠开头,因为它用于字符串插值 string interpolation。
julia> demand = "You owe me \$50!" "You owe me \$50!" julia> println(demand) You owe me $50!
julia> demandquote = "He said, \"You owe me \$50!\"" "He said, \"You owe me \$50!\""
字符串也可以用三个双引号括起来。这很有用,因为您可以在字符串中使用普通的双引号,而不必在它们前面加反斜杠:
julia> """this is "a" string""" "this is \"a\" string"
您还会遇到几种特殊类型的字符串,这些字符串由一个或多个字符组成,后面紧跟着开头的双引号:
r" "
表示这是一个正则表达式v" "
表示这是一个版本字符串b" "
表示这是一个字节字面量raw" "
表示这是一个 raw string 不允许插值
String interpolation
[编辑]您通常希望在字符串中使用 Julia 表达式的结果。例如,假设您想说:
"The value of x is n."
这 n
是 x
的当前值。任何 Julia 的表达式都可以插入到具有 $()
结构的字符串中:
julia> x = 42 42 julia> "The value of x is $(x)." "The value of x is 42."
如果只是使用变量的名称,则不必使用括号:
julia> "The value of x is $x." "The value of x is 42."
若要将 Julia 表达式的结果包含在字符串中,请先将表达式括在括号中,然后在其前面加上一个美元符号:
julia> "The value of 2 + 2 is $(2 + 2)." "The value of 2 + 2 is 4."
子字符串
[编辑]若要从字符串中提取较小的字符串,请使用 getindex(s, range)
或 s[range]
语法。对于基本ASCII字符串,可以使用用于从数组中提取元素的相同技术:
julia> s = String("a load of characters") "a load of characters" julia> s[1:end] "a load of characters" julia> s[3:6] "load"
julia> s[3:end-6] "load of char"
您可以轻松地遍历字符串:
for char in s
print(char, "_")
end
a_ _l_o_a_d_ _o_f_ _c_h_a_r_a_c_t_e_r_s_
注意,如果从字符串中提取单个元素,而不是长度为1的字符串(即具有相同的起始位置和结束位置):
julia> s[1:1] "a" julia> s[1] 'a'
第二个结果不是字符串,而是一个字符(在单引号内)。
Unicode 字符串
[编辑]并非所有字符串都是 ASCII。要访问 Unicode 字符串中的单个字符,不能总是使用简单索引,因为某些字符占用多个索引位置。不要仅仅因为一些 index 看起来会起作用就被愚弄了:
julia> su = String("AéB𐅍CD") "AéB𐅍CD" julia> su[1] 'A' julia> su[2] 'é' julia> su[3] ERROR: UnicodeError: invalid character index in slow_utf8_next(::Array{UInt8,1}, ::UInt8, ::Int64) at ./strings/string.jl:67 in next at ./strings/string.jl:92 [inlined] in getindex(::String, ::Int64) at ./strings/basic.jl:70
使用 lastindex(str)
而不是 length(str)
来查找字符串的长度:
julia> length(su) 6
julia> lastindex(su) 10
isascii()
函数用于测试字符串是 ASCII 还是包含 Unicode 字符:
julia> isascii(su) false
在这个字符串中,“第二个”字符 (é) 有2个字节,“第四个”字符(𐅍)有4个字节。
对于处理这样的字符串,有一些有用的函数,包括 thisind()
, nextind()
和 prevind()
:
for i in eachindex(su)
println(thisind(su, i), " -> ", su[i])
end
1 -> A
2 -> é
4 -> B
5 -> 𐅍
9 -> C
10 -> D
“第三个”字符 B 从字符串中的第四个元素开始。
此外,可以使用 eachindex
迭代器:
for charindex in eachindex(su)
@show su[charindex]
end
su[charindex] = 'A'
su[charindex] = 'é'
su[charindex] = 'B'
su[charindex] = '𐅍'
su[charindex] = 'C'
su[charindex] = 'D'
对字符串 split 和 join
[编辑]您可以使用乘 (*
) 运算符将字符串粘合在一起(通常称为串联的过程):
julia> "s" * "t" "st"
如果您使用过其他编程语言,您可能希望使用加法(+
)运算符:
julia> "s" + "t" LoadError: MethodError: `+` has no method matching +(::String, ::String)
- 因此用 *
.
如果你想把字符串‘乘’起来,你也可以把它们提升为一个幂:
julia> "s" ^ 18 "ssssssssssssssssss"
你也可以用 string()
:
julia> string("s", "t") "st"
但是,如果您想在循环中进行大量连接,也许最好使用字符串缓冲区方法(见下文)。
若要拆分字符串,请使用 split()
函数。给出这个简单的字符串:
julia> s = "You know my methods, Watson." "You know my methods, Watson."
对 split()
函数的简单调用在空格处分割字符串,返回一个分为五段的数组:
julia> split(s) 5-element Array{SubString{String},1}: "You" "know" "my" "methods," "Watson."
也可以指定要在以下位置拆分的 1个或多个字符的字符串:
julia> split(s, "e") 2-element Array{SubString{String},1}: "You know my m" "thods, Watson." julia> split(s, " m")' 3-element Array{SubString{String},1}: "You know" "y" "ethods, Watson."
用于分割的字符串不会显示在最终结果中:
julia> split(s, "hod") 2-element Array{SubString{String},1}: "You know my met" "s, Watson."
如果要将字符串拆分为单独的单字符串,请使用空字符串(“),它将字符串拆分为两个字符:
julia> split(s,"") 28-element Array{SubString{String},1}: "Y" "o" "u" " " "k" "n" "o" "w" " " "m" "y" " " "m" "e" "t" "h" "o" "d" "s" "," " " "W" "a" "t" "s" "o" "n" "."
也可以使用正则表达式来定义分割点来拆分字符串。使用特殊的正则表达式字符串结构 r" "
。在其中,您可以使用具有特殊含义的正则表达式字符:
julia> split(s, r"a|e|i|o|u") 8-element Array{SubString{String},1}: "Y" "" " kn" "w my m" "th" "ds, W" "ts" "n."
这里,r"a|e|i|o|u"
是一个正则表达式字符串,而且-如果您喜欢正则表达式的话-它与任何元音都匹配。因此,结果数组由每个元音处的字符串分割组成。注意结果中的空字符串-如果您不想要这些字符串,请在末尾添加一个 false 标志:
julia> split(s, r"a|e|i|o|u", false) 7-element Array{SubString{String},1}: "Y" " kn" "w my m" "th" "ds, W" "ts" "n."
如果您想保留元音,而不是将它们用于拆分工作,则必须深入研究正则表达式文字字符串的世界。继续读下去。
可以使用 join()
连接以数组形式拆分字符串的元素:
julia> join(split(s, r"a|e|i|o|u", false), "aiou") "Yaiou knaiouw my maiouthaiouds, Waioutsaioun."
Splitting using a function
[编辑]Julia中的许多函数允许您使用函数作为函数调用的一部分。匿名函数很有用,因为您可以进行具有内置智能选择的函数调用。例如,split()
允许您提供一个函数来代替分隔符字符。在下一个示例中,分隔符(奇怪地)指定为ASCII代码为8的倍数的任何大写字符:
julia> split(join(Char.(65:90)), c -> Int(c) % 8 == 0) 4-element Array{SubString{String},1}: "ABCDEFG" "IJKLMNO" "QRSTUVW" "YZ"
字符对象
[编辑]在上面,我们从较大的字符串中提取较小的字符串:
julia> s[1:1] "a"
但当我们从字符串中提取单个元素时:
julia> s[1] 'a'
请注意单引号。在Julia中,这些字符用于标记字符对象,因此‘a’是字符对象,而“a”是长度为1的字符串。他们是不相等的。
您可以轻松地将字符对象转换为字符串:
julia> string('s') * string('d') "sd"
或是
julia> string('s', 'd') "sd"
使用 \U
转义 输入32位 Unicode 字符很容易(大写表示32位)。小写转义序列 \u 可用于16位和8位字符:
julia> ('\U1014d', '\u2640', '\u26') ('𐅍','♀','&')
对于字符串,\Uxxxxxxx和\uxxxx语法更为严格。
julia> "\U0001014d2\U000026402\u26402\U000000a52\u00a52\U000000352\u00352\x352" "𐅍2♀2♀2¥2¥2525252"
数字和字符串之间的转换
[编辑]将整数转换为字符串也是 string()
函数的工作。关键字 base
用于指定转换的数字基数,可用于将十进制数字转换为二进制、八进制或十六进制字符串:
julia> string(11, base=2) "1011"
julia> string(11, base=8) "13" julia> string(11, base=16) "b" julia> string(11) "11"
julia> a = BigInt(2)^200 1606938044258990275541962092341162602522202993782792835301376
julia> string(a) "1606938044258990275541962092341162602522202993782792835301376"
julia> string(a, base=16) "1000000000000000000000000000000000000000000000000"
要将字符串转换为数字,请使用 parse()
,如果希望字符串被解释为使用数字基数,则还可以指定数字基数(如二进制或十六进制):
julia> parse(Int, "100") 100 julia> parse(Int, "100", base=2) 4 julia> parse(Int, "100", base=16) 256 julia> parse(Float64, "100.32") 100.32 julia> parse(Complex{Float64}, "0 + 1im") 0.0 + 1.0im
字符与整数互相转换
[编辑]Int()
将字符转换为整数,Char()
将整数转换为字符。
julia> Char(8253) '‽': Unicode U+203d (category Po: Punctuation, other) julia> Char(0x203d) # the Interrobang is Unicode U+203d in hexadecimal '‽': Unicode U+203d (category Po: Punctuation, other) julia> Int('‽') 8253 julia> string(Int('‽'), base=16) "203d"
要从单个字符串转换为代码号(如其 ASCII 或 UTF code number),请尝试以下操作:
julia> Int("S"[1]) 83
printf 格式
[编辑]如果您深深依赖于 C风格的 printf()
函数,那么您能够使用 Julia 宏(通过在宏前面加上 @
符号来调用它们)。宏在 Printf 包中提供,您需要先加载该程序包:
julia> using Printf
julia> @printf("pi = %0.20f", float(pi)) pi = 3.14159265358979311600
或者,也可以使用 sprintf()
宏创建另一个字符串,也可以在 Printf 包中找到:
julia> @sprintf("pi = %0.20f", float(pi)) "pi = 3.14159265358979311600"
将字符串转换为数组
[编辑]要将字符串读入数组,可以使用 IOBuffer()
函数。这在许多Julia函数(包括printf()
中都是可用的。下面是一串数据(可能是从文件中读取的):
data="1 2 3 4
5 6 7 8
9 0 1 2"
"1 2 3 4\n5 6 7 8\n9 0 1 2"
现在,您可以使用 readdlm()
之类的函数“读取”这个字符串,即“使用分隔符读取(read with delimiters)”函数。这可以在DelimitedFiles包中找到。
julia> using DelimitedFiles julia> readdlm(IOBuffer(data)) 3x4 Array{Float64,2}: 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 0.0 1.0 2.0
您可以添加一个可选的类型限定:
julia> readdlm(IOBuffer(data), Int) 3x4 Array{Int64,2}: 1 2 3 4 5 6 7 8 9 0 1 2
有时,您希望对字符串执行一些操作,以便使用数组做得更好。这里有一个例子。
julia> s = "/Users/me/Music/iTunes/iTunes Media/Mobile Applications";
可以使用 collect()
将路径名字符串 分解 为字符对象数组,该方法将集合中的项或字符串分解为数组:
julia> collect(s) 55-element Array{Char,1}: '/' 'U' 's' 'e' 'r' 's' '/' ...
类似地,您可以使用split()
拆分字符串并计算结果:
julia> split(s, "") 55-element Array{Char,1}: '/' 'U' 's' 'e' 'r' 's' '/' ...
要计算特定角色对象的出现次数,可以使用匿名函数:
julia> count(c -> c == '/', collect(s)) 6
虽然在这里转换为数组是不必要和低效的。这里有一个更好的方法:
julia> count(c -> c == '/', s) 6
查找和替换字符串中的内容
[编辑]如果您想知道字符串是否包含特定字符,请使用通用的 in()
函数。
julia> s = "Elementary, my dear Watson";
julia> in('m', s)
true
但是,接受两个字符串的 occursin()
函数更有用,因为您可以将子串与一个或多个字符一起使用。请注意,将搜索词放在第一位,然后是正在查找的字符串。occursin(needle, haystack)
:
julia> occursin("Wat", s) true
julia> occursin("m", s) true
julia> occursin("mi", s) false
julia> occursin("me", s) true
您可以使用 findfirst(needle, haystack)
获取子字符串第一次出现的位置。第一个参数可以是单个字符、字符串或正则表达式:
julia> s ="You know my methods, Watson."; julia> findfirst("meth", s) 13:16
julia> findfirst(r"[aeiou]", s) # first vowel 2
julia> findfirst(isequal('a'), s) # first occurrence of character 'a' 23
在每种情况下,结果都包含字符的索引(如果存在)。
替换
[编辑]replace()
函数的作用是:返回一个新字符串,其中包含一个替换为其他字符的子字符串:
julia> replace("Sherlock Holmes", "e" => "ee") "Sheerlock Holmees"
You use the => operator to specify the pattern you're looking for, and its replacement. Usually the third argument is another string, as here. But you can also supply a function that processes the result:
julia> replace("Sherlock Holmes", "e" => uppercase) "ShErlock HolmEs"
where the function (here, the built-in uppercase()
function) is applied to the matching substring.
There's no replace!
function, where the "!" indicates a function that changes its argument. That's because you can't change a string — they're immutable.
Replacing using functions
[编辑]Many functions in Julia allow you to supply functions as part of the function call, and you can make good use of anonymous functions for this. Here, for example, is how to use a function to provide random replacements in a replace()
function.
julia> t = "You can never foretell what any one man will do, but you can say with precision what an average number will be up to. Individuals vary, but percentages remain constant.";
julia> replace(t, r"a|e|i|o|u" => (c) -> rand(Bool) ? "0" : "1") "Y00 c1n n0v0r f1r0t1ll wh1t 0ny 0n0 m0n w1ll d0, b0t y01 c1n s1y w0th pr1c1s10n wh0t 1n 1v0r0g0 n1mb0r w0ll b0 0p t1. Ind1v0d11ls v0ry, b0t p1rc0nt0g0s r0m01n c1nst0nt."
julia> replace(t, r"a|e|i|o|u" => (c) -> rand(Bool) ? "0" : "1") "Y11 c0n...n1v0r f0r1t0ll wh1t 1ny 0n1 m0n w1ll d1, b1t y10 c1n s1y w1th pr0c1s01n wh0t 0n 0v1r0g0 n1mb1r w0ll b0 1p t1. Ind1v0d01ls v0ry, b1t p0rc1nt1g0s r0m01n c1nst0nt."
正则表达式
[编辑]You can use regular expressions to find matches for substrings. Some functions that accept a regular expression are:
replace()
changes occurrences of regular expressionsmatch()
returns the first match or nothingeachmatch()
returns an iterator that lets you search through all matchessplit()
splits a string at every match
Use replace()
to replace each consonant with an underscore:
julia> replace("Elementary, my dear Watson!", r"[^aeiou]" => "_") "__e_e__a________ea___a__o__"
and the following code replaces each vowel with the results of running a function on each match:
julia> replace("Elementary, my dear Watson!", r"[aeiou]" => uppercase) "ElEmEntAry, my dEAr WAtsOn!"
With replace()
you can access the matches if you provide a special substitution string s""
, where \1
refers to the first match, \2
to the second, and so on. With this regex operation, each lowercase letter preceded by a space is repeated three times:
julia> replace("Elementary, my dear Watson!", r"(\s)([a-z])" => s"\1\2\2\2") "Elementary, mmmy dddear Watson!"
For more regular expression fun, there are the -match-
functions.
Here I've loaded the complete text of "The Adventures of Sherlock Holmes" from a file into the string called text
:
julia> f = "/tmp/adventures-of-sherlock-holmes.txt" julia> text = read(f, String);
To use the possibility of a match as a Boolean condition, suitable for use in an if
statement for example, use occursin()
.
julia> occursin(r"Opium", text) false
That's odd. We were expecting to find evidence of the great detective's peculiar pharmacological recreations. In fact, the word "opium" does appear in the text, but only in lower-case, hence this false
result—regular expressions are case-sensitive.
julia> occursin(r"(?i)Opium", text) true
This is a case-insensitive search, set by the flag (?i)
), and it returns true
.
You could check every line for the word using a simple loop:
for l in split(text, "\n")
occursin(r"opium", l) && println(l)
end
opium. The habit grew upon him, as I understand, from some
he had, when the fit was on him, made use of an opium den in the
brown opium smoke, and terraced with wooden berths, like the
wrinkled, bent with age, an opium pipe dangling down from between
very short time a decrepit figure had emerged from the opium den,
opium-smoking to cocaine injections, and all the other little
steps - for the house was none other than the opium den in which
lives upon the second floor of the opium den, and who was
learn to have been the lodger at the opium den, and to have been
doing in the opium den, what happened to him when there, where is
"Had he ever showed any signs of having taken opium?"
room above the opium den when I looked out of my window and saw,
For more useable output (in the REPL), add enumerate()
and some highlighting:
red = Base.text_colors[:red]; default = Base.text_colors[:default];
for (n, l) in enumerate(split(text, "\n"))
occursin(r"opium", l) && println("$n $(replace(l, "opium" => "$(red)opium$(default)"))")
end
5087 opium. The habit grew upon him, as I understand, from some 5140 he had, when the fit was on him, made use of an opium den in the 5173 brown opium smoke, and terraced with wooden berths, like the 5237 wrinkled, bent with age, an opium pipe dangling down from between 5273 very short time a decrepit figure had emerged from the opium den, 5280 opium-smoking to cocaine injections, and all the other little 5429 steps - for the house was none other than the opium den in which 5486 lives upon the second floor of the opium den, and who was 5510 learn to have been the lodger at the opium den, and to have been 5593 doing in the opium den, what happened to him when there, where is 5846 "Had he ever showed any signs of having taken opium?" 6129 room above the opium den when I looked out of my window and saw,
There's an alternative syntax for adding regex modifiers, such as case-insensitive matches. Notice the "i" immediately following the regex string in the second example:
julia> occursin(r"Opium", text) false julia> occursin(r"Opium"i, text) true
With the eachmatch()
function, you apply the regex to the string to produce an iterator. For example, to look for substrings in our text matching the letters "L", followed by some other characters, ending with "ed":
julia> lmatch = eachmatch(r"L.*?ed", text)
The result in lmatch
is an iterable object containing all the matches, as RegexMatch objects:
julia> collect(lmatch)[1:10] 10-element Array{RegexMatch,1}: RegexMatch("London, and proceed") RegexMatch("London is a pleasant thing indeed") RegexMatch("Looking for lodgings,\" I answered") RegexMatch("London he had received") RegexMatch("Lied") RegexMatch("Life,\" and it attempted") RegexMatch("Lauriston Gardens wore an ill-omened") RegexMatch("Let\" card had developed") RegexMatch("Lestrade, is here. I had relied") RegexMatch("Lestrade grabbed")
We can step through the iterator and look at each match in turn. You can access a number of fields of a RegexMatch, to extract information about the match. These include captures
, match
, offset
, offsets
, and regex
. For example, the match
field contains the matched substring:
for i in lmatch
println(i.match)
end
London - quite so! Your Majesty, as I understand, became entangled
Lodge. As it pulled
Lord, Mr. Wilson, that I was a red
League of the Red
League was founded
London when he was young, and he wanted
LSON" in white letters, upon a corner house, announced
League, and the copying of the 'Encyclopaed
Leadenhall Street Post Office, to be left till called
Let the whole incident be a sealed
Lestrade, being rather puzzled
Lestrade would have noted
...
Lestrade," drawled
Lestrade looked
Lord St. Simon has not already arrived
Lord St. Simon sank into a chair and passed
Lord St. Simon had by no means relaxed
Lordship. "I may be forced
London. What could have happened
London, and I had placed
Other fields include captures
, the captured substrings as an array of strings, offset
,
the offset into the string at which the whole match begins, and offsets
, the offsets of the captured substrings.
To get an array of matching strings, use something like this:
julia> collect(m.match for m in eachmatch(r"L.*?ed", text)) 58-element Array{SubString{String},1}: "London - quite so! Your Majesty, as I understand, became entangled" "Lodge. As it pulled" "Lord, Mr. Wilson, that I was a red" "League of the Red" "League was founded" "London when he was young, and he wanted" "Leadenhall Street Post Office, to be left till called" "Let the whole incident be a sealed" "Lestrade, being rather puzzled" "Lestrade would have noted" "Lestrade looked" "Lestrade laughed" "Lestrade shrugged" "Lestrade called" ... "Lord St. Simon shrugged" "Lady St. Simon was decoyed" "Lestrade,\" drawled" "Lestrade looked" "Lord St. Simon has not already arrived" "Lord St. Simon sank into a chair and passed" "Lord St. Simon had by no means relaxed" "Lordship. \"I may be forced" "London. What could have happened" "London, and I had placed"
The basic match()
function looks for the first match for your regex. Use the match
field to extract the information from the RegexMatch object:
julia> match(r"She.*",text).match "Sherlock Holmes she is always THE woman. I have seldom heard\r"
A more streamlined way of obtaining matching lines from a file is this:
julia> f = "adventures of sherlock holmes.txt" julia> filter(s -> occursin(r"(?i)Opium", s), map(chomp, readlines(open(f)))) 12-element Array{SubString{String},1}: "opium. The habit grew upon him, as I understand, from some" "he had, when the fit was on him, made use of an opium den in the" "brown opium smoke, and terraced with wooden berths, like the" "wrinkled, bent with age, an opium pipe dangling down from between" "very short time a decrepit figure had emerged from the opium den," "opium-smoking to cocaine injections, and all the other little" "steps - for the house was none other than the opium den in which" "lives upon the second floor of the opium den, and who was" "learn to have been the lodger at the opium den, and to have been" "doing in the opium den, what happened to him when there, where is" "\"Had he ever showed any signs of having taken opium?\"" "room above the opium den when I looked out of my window and saw,"
Making a Regex
[编辑]Sometimes you want to make a regular expression from within your code. You can do this by making a Regex object. Here is one way you could count the number of vowels in the text:
f = open("sherlock-holmes.txt")
text = read(f, String)
for vowel in "aeiou"
r = Regex(string(vowel))
l = [m.match for m = eachmatch(r, thetext)]
println("there are $(length(l)) letter \"$vowel\"s in the text.")
end
there are 219626 letter "a"s in the text.
there are 337212 letter "e"s in the text.
there are 167552 letter "i"s in the text.
there are 212834 letter "o"s in the text.
there are 82924 letter "u"s in the text.
判断和更改字符串
[编辑]There are lots of functions for testing and changing strings:
length(str)
length of stringsizeof(str)
length/sizestartswith(strA, strB)
does strA start with strB?endswith(strA, strB)
does strA end with strB?occursin(strA, strB)
does strA occur in strB?all(isletter, str)
is str entirely letters?all(isnumeric, str)
is str entirely number characters?isascii(str)
is str ASCII?all(iscntrl, str)
is str entirely control characters?all(isdigit, str)
is str 0-9?all(ispunct, str)
does str consist of punctuation?all(isspace, str)
is str whitespace characters?all(isuppercase, str)
is str uppercase?all(islowercase, str)
is str entirely lowercase?all(isxdigit, str)
is str entirely hexadecimal digits?uppercase(str)
return a copy of str converted to uppercaselowercase(str)
return a copy of str converted to lowercasetitlecase(str)
return copy of str with the first character of each word converted to uppercaseuppercasefirst(str)
return copy of str with first character converted to uppercaselowercasefirst(str)
return copy of str with first character converted to lowercasechop(str)
return a copy with the last character removedchomp(str)
return a copy with the last character removed only if it's a newline
Streams
[编辑]To write to a string, you can use a Julia stream. The sprint()
(String Print) function lets you use a function as the first argument, and uses the function and the rest of the arguments to send information to a stream, returning the result as a string.
For example, consider the following function, f
. The body of the function maps an anonymous 'print' function over the arguments, enclosing them with angle brackets. When used by sprint
, the function f
processes the remaining arguments and sends them to the stream.
function f(io::IO, args...)
map((a) -> print(io,"<",a, ">"), args)
end
f (generic function with 1 method)
julia> sprint(f, "fred", "jim", "bill", "fred blogs") "<fred><jim><bill><fred blogs>"
Functions like println()
can take an IOBuffer or stream as their first argument. This lets you print to streams instead of printing to the standard output device:
julia> iobuffer = IOBuffer() IOBuffer(data=Uint8[...], readable=true, writable=true, seekable=true, append=false, size=0, maxsize=Inf, ptr=1, mark=-1)
julia> for i in 1:100 println(iobuffer, string(i)) end
After this, the in-memory stream called iobuffer
is full of numbers and newlines, even though nothing was printed on the terminal. To copy the contents of iobuffer
from the stream to a string or array, you can use take!()
:
julia> String(take!(iobuffer)) "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14 ... \n98\n99\n100\n"
Working with Text Files
[编辑]
从文件中读取
[编辑]从文本文件获取信息的标准方法是使用 open()
, read()
和 close()
函数.
打开
[编辑]要从文件中读取文本,请首先获取文件的 handle:
f = open("sherlock-holmes.txt")
f
现在是 Julia 与磁盘上的文件的连接。完成文件处理后,应使用以下命令关闭连接:
close(f)
通常,在 Julia 中处理文件的推荐方法是将任何文件处理函数包装在 do 代码块中:
open("sherlock-holmes.txt") do file
# do stuff with the open file
end
当此代码块完成时,打开的文件将自动关闭。更多有关 do
block 内容,见 Controlling the flow。
由于块中局部变量的范围,您可能希望保留一些已处理的信息:
totaltime, totallines = open("sherlock-holmes.txt") do f
linecounter = 0
timetaken = @elapsed for l in eachline(f)
linecounter += 1
end
(timetaken, linecounter)
end
julia> totaltime, totallines (0.004484679, 76803)
一次性读取整个文件 !
[编辑]可以使用 read()
一次性读取打开文件的全部内容:
julia> s = read(f, String)
这会将文件的内容存储在 s
中:
s = open("sherlock-holmes.txt") do file
read(file, String)
end
你可以使用 readlines()
来以数组的形式读取整个文件,并且每行都有一个元素,请执行以下操作:
julia> f = open("sherlock-holmes.txt"); julia> lines = readlines(f) 76803-element Array{String,1}: "THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE\r\n" "\r\n" " I. A Scandal in Bohemia\r\n" " II. The Red-headed League\r\n" ... "Holmes, rather to my disappointment, manifested no further\r\n" "interest in her when once she had ceased to be the centre of one\r\n" "of his problems, and she is now the head of a private school at\r\n" "Walsall, where I believe that she has met with considerable success.\r\n" julia> close(f)
现在,您可以对每行执行单步操作:
counter = 1
for l in lines
println("$counter $l")
counter += 1
end
1 THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2
3 I. A Scandal in Bohemia
4 II. The Red-headed League
5 III. A Case of Identity
6 IV. The Boscombe Valley Mystery
...
12638 interest in her when once she had ceased to be the centre of one
12639 of his problems, and she is now the head of a private school at
12640 Walsall, where I believe that she has met with considerable success.
有一种更好的方法可以做到这一点-参见下面的 enumerate()
。
您可能会发现 chomp()
函数很有用-它从字符串中删除后面的换行符。
一行行读取
[编辑]The eachline()
function turns a source into an iterator. This allows you to process a file a line at a time:
open("sherlock-holmes.txt") do file
for ln in eachline(file)
println("$(length(ln)), $(ln)")
end
end
1, THE ADVENTURES OF SHERLOCK HOLMES by SIR ARTHUR CONAN DOYLE
2,
28, I. A Scandal in Bohemia
29, II. The Red-headed League
26, III. A Case of Identity
35, IV. The Boscombe Valley Mystery
…
62, the island of Mauritius. As to Miss Violet Hunter, my friend
60, Holmes, rather to my disappointment, manifested no further
66, interest in her when once she had ceased to be the centre of one
65, of his problems, and she is now the head of a private school at
70, Walsall, where I believe that she has met with considerable success.
另一种方法是一直读到文件的末尾。您可能需要跟踪您所在的行:
open("sherlock-holmes.txt") do f
line = 1
while !eof(f)
x = readline(f)
println("$line $x")
line += 1
end
end
一种更好的方法是在可迭代对象上使用 enumerate()
-您将额外得到一个的行号:
open("sherlock-holmes.txt") do f
for i in enumerate(eachline(f))
println(i[1], ": ", i[2])
end
end
如果您有一个特定的函数要对文件调用,则可以使用以下语法:
function shout(f::IOStream)
return uppercase(read(f, String))
end
julia> shoutversion = open(shout, "sherlock-holmes.txt");
julia> shoutversion[30237:30400] "ELEMENTARY PROBLEMS. LET HIM, ON MEETING A\nFELLOW-MORTAL, LEARN AT A GLANCE TO DISTINGUISH THE HISTORY OF THE\nMAN, AND THE TRADE OR PROFESSION TO WHICH HE BELONGS. "
这将打开该文件,对其运行 shout()
函数,然后再次关闭该文件,并将处理后的内容分配给该变量。
You can use the DelimitedFiles.readdlm()
function to read lines delimited with certain characters, such as data files, arrays stored as text files, and tables. If you use the DataFrames package, there's also a readtable()
specifically designed to read data into a table. See also the package CSV.jl.
对路径和文件名进行操作
[编辑]These functions will be useful for working with filenames:
cd(path)
changes the current directoryreaddir(path)
returns a lists of the contents of a named directory, or the current directory,abspath(path)
adds the current directory's path to a filename to make an absolute pathnamejoinpath(str, str, ...)
assembles a pathname from piecesisdir(path)
tells you whether the path is a directorysplitdir(path)
- split a path into a tuple of the directory name and file name.splitdrive(path)
- on Windows, split a path into the drive letter part and the path part. On Unix systems, the first component is always the empty string.splitext(path)
- if the last component of a path contains a dot, split the path into everything before the dot and everything including and after the dot. Otherwise, return a tuple of the argument unmodified and the empty string.expanduser(path)
- replace a tilde character at the start of a path with the current user's home directory.normpath(path)
- normalize a path, removing "." and ".." entries.realpath(path)
- canonicalize a path by expanding symbolic links and removing "." and ".." entries.homedir()
- current user's home directory.dirname(path)
- get the directory part of a path.basename(path)
- get the file name part of a path.
To work on a restricted selection of files in a directory, use filter()
and an anonymous function to filter the file names and just keep the ones you want. (filter()
is more of a fishing net or sieve, rather than a coffee filter, in that it catches what you want to keep.)
for f in filter(x -> endswith(x, "jl"), readdir())
println(f)
end
Astro.jl
calendar.jl
constants.jl
coordinates.jl
...
pseudoscience.jl
riseset.jl
sidereal.jl
sun.jl
utils.jl
vsop87d.jl
If you want to match a group of files using a regular expression, then use occursin()
. Let's look for files with ".jpg" or ".png" suffixes (remembering to escape the "."):
for f in filter(x -> occursin(r"(?i)\.jpg|\.png", x), readdir())
println(f)
end
034571172750.jpg
034571172750.png
51ZN2sCNfVL._SS400_.jpg
51bU7lucOJL._SL500_AA300_.jpg
Voronoy.jpg
kblue.png
korange.png
penrose.jpg
r-home-id-r4.png
wave.jpg
To examine a file hierarchy, use walkdir()
, which lets you work through a directory, and examine the files in each directory in turn.
文件信息
[编辑]If you want information about a specific file, use stat("pathname")
, and then use one of the fields to find out the information. Here's how to get all the information and the field names listed for a file "i":
for n in fieldnames(typeof(stat("i")))
println(n, ": ", getfield(stat("i"),n))
end
device: 16777219
inode: 2955324
mode: 16877
nlink: 943
uid: 502
gid: 20
rdev: 0
size: 32062
blksize: 4096
blocks: 0
mtime:1.409769933e9
ctime:1.409769933e9
You can access these fields via a 'stat' structure:
julia> s = stat("Untitled1.ipynb") StatStruct(mode=100644, size=64424)
julia> s.ctime 1.446649269e9
and you can also use some of them directly:
julia> ctime("Untitled2.ipynb") 1.446649269e9
although not size
:
julia> s.size 64424
To work on specific files that meet conditions — all IPython files modified after a certain date, for example — you could use something like this:
using Dates
function output_file(path)
println(stat(path).size, ": ", path)
end
for afile in filter!(f -> endswith(f, "ipynb") && (mtime(f) > Dates.datetime2unix(DateTime("2015-11-03T09:00"))),
readdir())
output_file(realpath(afile))
end
与文件系统交互
[编辑]The cp()
, mv()
, rm()
, and touch()
functions have the same names and functions as their Unix shell counterparts.
To convert filenames to pathnames, use abspath()
. You can map this over a list of files in a directory:
julia> map(abspath, readdir()) 67-element Array{String,1}: "/Users/me/.CFUserTextEncoding" "/Users/me/.DS_Store" "/Users/me/.Trash" "/Users/me/.Xauthority" "/Users/me/.ahbbighrc" "/Users/me/.apdisk" "/Users/me/.atom" ...
To restrict the list to filenames that contain a particular substring, use an anonymous function inside filter()
— something like this:
julia> filter(x -> occursin("re", x), map(abspath, readdir())) 4-element Array{String,1}: "/Users/me/.DS_Store" "/Users/me/.gitignore" "/Users/me/.hgignore_global" "/Users/me/Pictures" ...
To restrict the list to regular expression matches, try this:
julia> filter(x -> occursin(r"recur.*\.jl", x), map(abspath, readdir())) 2-element Array{String,1}: "/Users/me/julia/recursive-directory-scan.jl" "/Users/me/julia/recursive-text.jl"
写入文件
[编辑]To write to a text file, open it using the "w" flag and make sure that you have permission to create the file in the specified directory:
open("/tmp/t.txt", "w") do f
write(f, "A, B, C, D\n")
end
Here's how to write 20 lines of 4 random numbers between 1 and 10, separated by commas:
function fourrandom()
return rand(1:10,4)
end
open("/tmp/t.txt", "w") do f
for i in 1:20
n1, n2, n3, n4 = fourrandom()
write(f, "$n1, $n2, $n3, $n4 \n")
end
end
A quicker alternative to this is to use the DelimitedFiles.writedlm()
function, described next:
using DelimitedFiles
writedlm("/tmp/test.txt", rand(1:10, 20, 4), ", ")
在文件中写入和读取数组
[编辑]In the DelimitedFiles package are two convenient functions, writedlm()
and readdlm()
. These let you read/write an array or collection from/to a file.
writedlm()
writes the contents of an object to a text file, and readdlm()
reads the data from a file into an array:
julia> numbers = rand(5,5) 5x5 Array{Float64,2}: 0.913583 0.312291 0.0855798 0.0592331 0.371789 0.13747 0.422435 0.295057 0.736044 0.763928 0.360894 0.434373 0.870768 0.469624 0.268495 0.620462 0.456771 0.258094 0.646355 0.275826 0.497492 0.854383 0.171938 0.870345 0.783558 julia> writedlm("/tmp/test.txt", numbers)
You can see the file using the shell (type a semicolon ";" to switch):
<shell> cat "/tmp/test.txt" .9135833328830523 .3122905420350348 .08557977218948465 .0592330821115965 .3717889559226475 .13747015238054083 .42243494637594203 .29505701073304524 .7360443978397753 .7639280496847236 .36089432672073607 .43437288984307787 .870767989032692 .4696243851552686 .26849468736154325 .6204624598015906 .4567706404666232 .25809436255988105 .6463554854347682 .27582613759302377 .4974916625466639 .8543829989347014 .17193814498701587 .8703447748713236 .783557793485824
The elements are separated by tabs unless you specify another delimiter. Here, a colon is used to delimit the numbers:
julia> writedlm("/tmp/test.txt", rand(1:6, 10, 10), ":")
shell> cat "/tmp/test.txt" 3:3:3:2:3:2:6:2:3:5 3:1:2:1:5:6:6:1:3:6 5:2:3:1:4:4:4:3:4:1 3:2:1:3:3:1:1:1:5:6 4:2:4:4:4:2:3:5:1:6 6:6:4:1:6:6:3:4:5:4 2:1:3:1:4:1:5:4:6:6 4:4:6:4:6:6:1:4:2:3 1:4:4:1:1:1:5:6:5:6 2:4:4:3:6:6:1:1:5:5
To read in data from a text file, you can use readdlm()
.
julia> numbers = rand(5,5) 5x5 Array{Float64,2}: 0.862955 0.00827944 0.811526 0.854526 0.747977 0.661742 0.535057 0.186404 0.592903 0.758013 0.800939 0.949748 0.86552 0.113001 0.0849006 0.691113 0.0184901 0.170052 0.421047 0.374274 0.536154 0.48647 0.926233 0.683502 0.116988
julia> writedlm("/tmp/test.txt", numbers) julia> numbers = readdlm("/tmp/test.txt") 5x5 Array{Float64,2}: 0.862955 0.00827944 0.811526 0.854526 0.747977 0.661742 0.535057 0.186404 0.592903 0.758013 0.800939 0.949748 0.86552 0.113001 0.0849006 0.691113 0.0184901 0.170052 0.421047 0.374274 0.536154 0.48647 0.926233 0.683502 0.116988
There are also a number of Julia packages specifically designed for reading and writing data to files, including DataFrames.jl and CSV.jl. Look through the Julia package directory for these and more.
Working with Dates and Times
[编辑]
Working with dates and times
[编辑]Functions for working with dates and times are provided in the standard package Dates. To use any of the time and date functions, you must do one of the following:
using Dates
import Dates
If you use import
Dates functions, you’ll need to prefix every function with an explicit Dates., e.g. Dates.dayofweek(dt)
, as shown in this chapter. However, if you add the line using Dates
to your code, this brings all exported Dates functions into Main, and they can be used without the Dates.
prefix.
类型
[编辑]This diagram shows the relationship between the various types used to store Times, Dates, and DateTimes.
Date, Time 以及 DateTimes
[编辑]There are three main datatypes available:
- A Dates.Time object represents a precise moment of time in a day. It doesn't say anything about the day of the week, or the year, though. It's accurate to a nanosecond.
- A Dates.Date object represents just a date: no time zones, no daylight saving issues, etc... It's accurate to, well, a day.
- A Dates.DateTime object is a combination of a date and a time of day, and so it specifies an exact moment in time. It's accurate to a millisecond or so.
Use one of these constructors to make the type of object you want:
julia> rightnow = Dates.Time(Dates.now()) # a Dates.Time object 16:51:56.374
julia> birthday = Dates.Date(1997,3,15) # a Dates.Date object 1997-03-15 julia> armistice = Dates.DateTime(1918,11,11,11,11,11) # a Dates.DateTime object 1918-11-11T11:11:11
The Dates.today()
function returns a Date object for the current date:
julia> datetoday = Dates.today() 2014-09-02
The Dates.now()
function returns a DateTime object for the current instant in time:
julia> datetimenow = Dates.now() 2014-09-02T08:20:07.437
(We used Dates.now()
earlier to define rightnow
, then converted it to a Dates.Time using Dates.Time().)
Sometimes you want UTC (the reference time for the world, without local adjustments for daylight savings):
julia> Dates.now(Dates.UTC) 2014-09-02T08:27:54.14
To create an object from a formatted string, use the DateTime()
function in Dates, and supply a suitable format string that matches the formatting:
julia> Dates.DateTime("20140529 120000", "yyyymmdd HHMMSS") 2014-05-29T12:00:00 julia> Dates.DateTime("18/05/2009 16:12", "dd/mm/yyyy HH:MM") 2009-05-18T16:12:00 julia> vacation = Dates.DateTime("2014-09-02T08:20:07") # defaults to expecting ISO8601 format 2014-09-02T08:20:07
See Date Formatting below for more examples.
日期和时间查询
[编辑]Once you have a date/time or date object, you can extract information from it with the following functions. For both date and datetime objects, you can obtain the year, month, day, and so on:
julia> Dates.year(birthday) 1997 julia> Dates.year(datetoday) 2014 julia> Dates.month(birthday) 3 julia> Dates.month(datetoday) 9 julia> Dates.day(birthday) 15 julia> Dates.day(datetoday) 2
and, for date/time objects:
julia> Dates.minute(now()) 37 julia> Dates.hour(now()) 16 julia> Dates.second(now()) 8
julia> Dates.minute(rightnow) 37 julia> Dates.hour(rightnow) 16 julia> Dates.second(rightnow) 8
There's also a bunch of other useful ones:
julia> Dates.dayofweek(birthday) 6 julia> Dates.dayname(birthday) "Saturday" julia> Dates.yearmonth(now()) (2014,9) julia> Dates.yearmonthday(birthday) (1997,3,15) julia> Dates.isleapyear(birthday) false julia> Dates.daysofweekinmonth(datetoday) 5 julia> Dates.monthname(birthday) "March" julia> Dates.monthday(now()) (9,2) julia> Dates.dayofweekofmonth(birthday) 3
Two of those functions are very similarly named: the Dates.daysofweekinmonth()
(days of week in month) function tells you how many days there are in the month with the same day name as the specified day — there are five Tuesdays in the current month (at the time of writing). The last function, dayofweekofmonth(birthday)
(day of week of month), tells us that the 15th of March, 1997, was the third Saturday of the month.
You can also find days relative to a date, such as the first day of the week containing that day, using the adjusting functions, described below.
日期计算
[编辑]You can do arithmetic on dates and date/time objects. Subtracting two dates or datetimes to find the difference is the most obvious one:
julia> datetoday - birthday 6380 days julia> datetimenow - armistice 3023472252000 milliseconds
which you can convert to Dates.Day
s or Dates.Millisecond
s or some other unit:
julia> Dates.Period(datetoday - birthday) 7357 days julia> Dates.canonicalize(Dates.CompoundPeriod(datetimenow - armistice)) 5138 weeks, 5 days, 5 hours, 46 minutes, 1 second, 541 milliseconds julia> convert(Dates.Day, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1))) 491 days julia> convert(Dates.Millisecond, Dates.Period(Dates.today() - Dates.Date(2016, 1, 1))) 42422400000 milliseconds
To add and subtract periods of time to date and date/time objects, use the Dates.
constructor functions to specify the period. For example, Dates.Year(20)
defines a period of 20 years, and Dates.Month(6)
defines a period of 6 months. So, to add 20 years and 6 months to the birthday date:
julia> birthday + Dates.Year(20) + Dates.Month(6) 2017-09-15
Here's 6 months ago from now:
julia> Dates.now() - Dates.Month(6) 2014-03-02T16:43:08
and similarly for months, weeks:
julia> Dates.now() - Dates.Year(2) - Dates.Month(6) 2012-03-02T16:44:03
and similarly for weeks and hours. Here's the date and time for two weeks and 12 hours from now:
julia> Dates.now() + Dates.Week(2) + Dates.Hour(12) 2015-09-18T20:49:16
and there are
julia> daystoxmas = Dates.Date.(Dates.year(Dates.now()), 12, 25) - Dates.today() 148 days
or 148 (shopping) days till Christmas (at the time this was written).
To retrieve the value as a number, use the function Dates.value()
:
julia> Dates.value(daystoxmas) 148
This works with different types of date/time objects too:
julia> lastchristmas = Dates.now() - Dates.DateTime(2017, 12, 25, 0, 0, 0) 25464746504 milliseconds julia> Dates.value(lastchristmas) 25464746504
日期范围
[编辑]You can make iterable range objects that define a range of dates:
julia> d = Dates.Date(1980,1,1):Dates.Month(3):Dates.Date(2019,1,1) 1980-01-01:3 months:2019-01-01
This iterator yields the first day of every third month. To find out which of these fall on weekdays, you can provide an anonymous function to filter()
that tests the day name against the given day names:
julia> weekdays = filter(dy -> Dates.dayname(dy) != "Saturday" && Dates.dayname(dy) != "Sunday" , d)
104-element Array{Date,1}:
1980-01-01 1980-04-01 1980-07-01 ⋮ 2014-07-01 2014-10-01 2016-04-01 2016-07-01 2018-01-01 2018-10-01 2019-01-01
Similarly, here's a range of times 3 hours apart from now, for a year hence:
julia> d = collect(Dates.DateTime(Dates.now()):Dates.Hour(3):Dates.DateTime(Dates.now() + Dates.Year(1))) 2929-element Array{DateTime,1}: 2015-09-04T08:30:59 2015-09-04T11:30:59 2015-09-04T14:30:59 ⋮ 2016-09-03T20:30:59 2016-09-03T23:30:59 2016-09-04T02:30:59 2016-09-04T05:30:59 2016-09-04T08:30:59
If you have to pay a bill every 30 days, starting on the 1st of January 2018, the following code shows how the due date creeps forward every month:
julia> foreach(d -> println(Dates.format(d, "d u yyyy")), Dates.Date("2018-01-01"):Dates.Day(30):Dates.Date("2019-01-01")) 1 Jan 2018 31 Jan 2018 2 Mar 2018 1 Apr 2018 1 May 2018 31 May 2018 30 Jun 2018 30 Jul 2018 29 Aug 2018 28 Sep 2018 28 Oct 2018 27 Nov 2018 27 Dec 2018
日期格式化
[编辑]To specify date formats, you use date formatting codes in a formatting string. Each character refers to a date/time element:
y Year digit eg yyyy => 2015, yy => 15 m Month digit eg m => 3 or 03 u Month name eg Jan U Month name eg January e Day of week eg Tue E Day of week eg Tuesday d Day eg 3 or 03 H Hour digit eg HH => 00 M Minute digit eg MM => 00 S Second digit eg S => 00 s Millisecond digit eg .000
You can use these formatting strings with functions such as DateTime()
and Dates.format()
. For example, you create a DateTime object from a string by identifying the different elements in the incoming string:
julia> Dates.Date("Fri, 15 Jun 2018", "e, d u y") 2018-06-15
julia> Dates.DateTime("Fri, 15 Jun 2018 11:43:14", "e, d u y H:M:S") 2018-06-15T11:43:14
Other characters are used literally. In the second example, the formatting characters matched up as follows:
Fri, 15 Jun 2018 11:43:14 e , d u y H: M: S
You can supply a format string to Dates.format
to format a date object. In the formatting string, you repeat the characters to control how years and days, for example, are output:
julia> timenow = Dates.now() 2015-07-28T11:43:14
julia> Dates.format(timenow, "e, dd u yyyy HH:MM:SS") "Tue, 28 Jul 2015 11:43:14"
When you're creating a formatted date, you can double some of the components of the format string to produce a leading zero for single digit date elements:
julia> anothertime = Dates.DateTime("Tue, 8 Jul 2015 2:3:7", "e, d u y H:M:S") 2015-07-08T02:03:07 julia> Dates.format(anothertime, "e: dd u yy, HH.MM.SS") # with leading zeros "Wed: 08 Jul 15, 02.03.07" julia> Dates.format(anothertime, "e: d u yy, H.M.S") "Wed: 8 Jul 15, 2.3.7"
To convert a date string from one format to another, you can use DateTime()
and a format string to convert the string to a DateTime object, then DateFormat()
to output the object in a different format:
julia> formatted_date = "Tue, 28 Jul 2015 11:43:14" "Tue, 28 Jul 2015 11:43:14" julia> temp = Dates.DateTime(formatted_date, "e, dd u yyyy HH:MM:SS") 2015-07-28T11:43:14 julia> Dates.format(temp, "dd, U, yyyy HH:MM, e") "28, July, 2015 11:43, Tue"
If you're doing a lot of date formatting (you can apply date functions to an array of strings), it's a good idea to pre-define a DateFormat object and then use that for bulk conversions (this is quicker):
julia> dformat = Dates.DateFormat("y-m-d"); julia> Dates.Date.([ # broadcast "2010-01-01", "2011-03-23", "2012-11-3", "2013-4-13", "2014-9-20", "2015-3-1" ], dformat)
6-element Array{Date,1}: 2010-01-01 2011-03-23 2012-11-03 2013-04-13 2014-09-20 2015-03-01
There are some built-in formats that you can use. For example, there's Dates.ISODateTimeFormat
to give you the ISO8601 format:
julia> Dates.DateTime.([ "2010-01-01", "2011-03-23", "2012-11-3", "2013-4-13", "2014-9-20", "2015-3-1" ], Dates.ISODateTimeFormat) 6-element Array{DateTime,1}: 2010-01-01T00:00:00 2011-03-23T00:00:00 2012-11-03T00:00:00 2013-04-13T00:00:00 2014-09-20T00:00:00 2015-03-01T00:00:00</syntaxhighlight>
and here's good old RFC1123:
julia> Dates.format(Dates.now(), Dates.RFC1123Format) "Sat, 30 Jul 2016 16:36:09"
日期调整
[编辑]Sometimes you want to find a date nearest to another - for example, the first day of that week, or the last day of the month that contains that date. You can do this with the functions like Dates.firstdayofweek()
and Dates.lastdayofmonth()
. So, if we're currently in the middle of the week:
julia> Dates.dayname(now()) "Wednesday"
the first day of the week is returned by this:
julia> Dates.firstdayofweek(now()) 2014-09-01T00:00:00
which you could also write using the function chain operator:
julia> Dates.now() |> Dates.firstdayofweek |> Dates.dayname "Monday"
A more general solution is provided by the tofirst()
, tolast()
, tonext()
, and toprev()
methods.
With tonext()
and toprev()
, you can provide a (possibly anonymous) function that returns true when a date has been correctly adjusted. For example, the function:
d->Dates.dayofweek(d) == Dates.Tuesday
returns true if the day d
is a Tuesday. Use this with the tonext()
method:
julia> Dates.tonext(d->Dates.dayofweek(d) == Dates.Tuesday, birthday) 1997-03-18 # the first Tuesday after the birthday
Or you can find the next Sunday following the birthday date:
julia> Dates.tonext(d->Dates.dayname(d) == "Sunday", birthday) 1997-03-16 # the first Sunday after the birthday
With tofirst()
and tolast()
, you can find the first Sunday, or Thursday, or whatever, of a month. Monday is 1, Tuesday 2, etc.
julia> Dates.tofirst(birthday, 1) # the first Monday (1) of that month 1997-03-03
Supply the keyword argument of=Year
to get the first matching weekday of the year.
julia> Dates.tofirst(birthday, 1, of=Year) # the first Monday (1) of 1997 1997-01-06
日期和时间的四舍五入
[编辑]You can use round()
, floor()
, and ceil()
, usually used to round numbers up or down to the nearest preferred values, to adjust dates forward or backwards in time so that they have 'rounder' values.
julia> Dates.now() 2016-09-12T17:55:11.378 julia> Dates.format(round(Dates.DateTime(Dates.now()), Dates.Minute(15)), Dates.RFC1123Format) "Mon, 12 Sep 2016 18:00:00"
The ceil()
adjusts dates or times forward in time:
julia> ceil(birthday, Dates.Month) 1997-04-01 julia> ceil(birthday, Dates.Year) 1998-01-01 julia> ceil(birthday, Dates.Week) 1997-03-17
Recurring dates
[编辑]It's useful to be able to find all dates in a range of dates that satisfy some particular criteria. For example, you can work out the second Sunday in a month by using the Dates.dayofweekofmonth()
and Dates.dayname()
functions.
For example, let's create a range of dates from the first of September 2014 until Christmas Day, 2014:
julia> dr = Dates.Date(2014,9,1):Dates.Day(1):Dates.Date(2014,12,25) 2014-09-01:1 day:2014-12-25
Now an anonymous function similar to the ones we used in tonext()
earlier finds a selection of those dates in that range that satisfy that function:
julia> filter(d -> Dates.dayname(d) == "Sunday", dr) 16-element Array{Date,1}: 2014-09-07 2014-09-14 2014-09-21 2014-09-28 2014-10-05 2014-10-12 2014-10-19 2014-10-26 2014-11-02 2014-11-09 2014-11-16 2014-11-23 2014-11-30 2014-12-07 2014-12-14 2014-12-21
These are the dates of every Sunday between September 1st 2014 until Christmas Day, 2014.
By combining criteria in the anonymous function, you can build up more complicated recurring events. Here's a list of all the Tuesdays in that period which are on days that are odd numbered and greater than 20:
julia> filter(d->Dates.dayname(d) == "Tuesday" && isodd(Dates.day(d)) && Dates.day(d) > 20, dr) 4-element Array{Date,1}: 2014-09-23 2014-10-21 2014-11-25 2014-12-23
and here's every second Tuesday in 2016 between April and November:
dr = Dates.Date(2015):Dates.Day(1):Dates.Date(2016);
filter(dr) do x
Dates.dayofweek(x) == Dates.Tue &&
Dates.April <= Dates.month(x) <= Dates.Nov &&
Dates.dayofweekofmonth(x) == 2
end
8-element Array{Base.Dates.Date,1}:
2015-04-14
2015-05-12
2015-06-09
2015-07-14
2015-08-11
2015-09-08
2015-10-13
2015-11-10
Unix 时间
[编辑]You sometimes have to deal with another type of timekeeping: Unix time. Unix time is a count of the number of seconds that have elapsed since the beginning of the year 1970 (the birth of Unix). In Julia the count is stored in a 64 bit integer, and we'll never see the end of Unix time. (The universe will have ended long before 64 bit Unix time reaches the maximum possible value, which will be in approximately 292 billion years from now, at 15:30:08 on Sunday, 4 December 292,277,026,596.)
In Julia, the time()
function, used without arguments, returns the Unix time value of the current second:
julia> time() 1.414141581230945e9
The strftime()
("string format time") function, which lives in the Libc module, converts a number of seconds in Unix time to a more readable form:
julia> Libc.strftime(86400 * 365.25 * 4) # 4 years worth of Unix seconds "Tue 1 Jan 00:00:00 1974"
You can choose a different format by supplying a format string, with the different components of the date and time defined by '%' letter codes:
julia> Libc.strftime("%A, %B %e at %T, %Y", 86400 * 365.25 * 4) "Tuesday, January 1 at 00:00:00, 1974"
The strptime()
function takes a format string and a date string, and returns a TmStruct expression. This can then be converted to a Unix time value by passing it to time()
:
julia> Libc.strptime("%A, %B %e at %T, %Y", "Tuesday, January 1 at 00:00:00, 1974") Base.Libc.TmStruct(0,0,0,1,0,74,2,0,0,0,0,0,0,0) julia> time(ans) 1.262304e8 julia> time(Libc.strptime("%Y-%m-%d","2014-10-1")) 1.4121216e9
The Dates module also offers a unix2datetime()
function, which converts a Unix time value to a date/time object:
julia> Dates.unix2datetime(time()) 2014-10-24T09:26:29.305
时刻
[编辑]DateTime
s are stored as milliseconds. You can access the field instant
to see the actual value.
julia> moment=Dates.now() 2017-02-01T12:45:46.326 julia> moment.instant Base.Dates.UTInstant{Base.Dates.Millisecond}(63621636346326 milliseconds) julia> Dates.value(moment) 63621636346326
If you use the more precise Dates.Time
type, you can access nanoseconds.
julia> moment = Dates.Time(Dates.now()) 17:38:44.33 julia> moment.instant 63524330000000 nanoseconds julia> Dates.value(moment) 63524330000000
计时与监测
[编辑]The @elapsed
macro returns the number of seconds an expression took to evaluate:
function test(n)
for i in 1:n
x = sin(rand())
end
end
julia> @elapsed test(100000000) 1.309819509
The @time
macro tells you how long an expression took to evaluate, and how memory was allocated.
julia> @time test(100000000) 2.532941 seconds (4 allocations: 160 bytes)
Plotting
[编辑]
绘图
[编辑]Julia 有许多不同的绘图包,应该会有一款适合您的需要,合您口味。本节是对其中一种绘图包 Plots.jl 的快速介绍,它非常有趣,因为它与许多其他绘图软件包进行了交互。在使用Julia进行绘图之前,请下载并安装一些绘图包 (请先在 REPL 中键入 ] 进入 pkg 模式)
(v1.0) pkg> add Plots PyPlot GR UnicodePlots
第一个包,Plots,是一个高级绘图包,它为其他绘图包 (这里称为“后端”backend )提供一个统一的接口,而这些后端则充当生成图形的图形“引擎”。每个后端都是一个独立的绘图包,可以单独使用,但使用 Plots 的优点是,它提供给您一个更简单更一致的接口。
通过下面的代码,您可以开始在 Julia 会话中使用 Plots.jl 这个绘图包。
julia> using Plots
一般来说,您会需要将一个或者多个 序列 (即数值数组)绘制成图,或者也可以提供一个或多个函数来生成这些数值。
如果您想继续探究,这里使用的示例数据是一个简单的数值数组,表示当前年份中每天的 均时差 的值。(这些值曾用于调整机械时钟,以解释地球绕椭圆轨道摆动时的不稳定轨道。)
julia> using Astro # you'll need to add this package with: add https://github.com/cormullion/Astro.jl julia> using Dates
julia> days = Dates.datetime2julian.(Dates.DateTime(2018, 1, 1, 0, 0, 0):Dates.Day(1):Dates.DateTime(2018, 12, 31, 0, 0, 0)) julia> eq_values = map(equation_time, days)
我们现在有了一组 Float64 类型的数据,每个数据表示一年中每天的数值:
365-element Array{Float64,1}: -3.12598 -3.59633 -4.97289 -5.41857 -5.85688 ⋮ -1.08709 -1.57435 -2.05845 -2.53887 -3.01508
要对这一组数据绘图,只需要把它传入到 Plots 的 plot()
函数中。
julia> plot(eq_values)
这使用了第一个可用的绘图引擎。Plot 将该系列视为 y 值,添加了其他打印值,自动提供与您提供的 y 值相匹配的 x 值,然后为您打印所有内容。
如果你想要选择不同的绘图引擎,使用提供的几个函数 gr()
, unicodeplots()
, plotly()
以及其他的。例如,选择 Unicodeplots 绘图包(会使用 Unicode 字符绘图,这是在 REPL/终端 上的最佳选择):
julia> unicodeplots() julia> plot(eq_values)
+------------------------------------------------------------+ 17 | ,--u | y1 | ./ "\ | | ./ \ | | ./ . | | / \. | | / \ | | .` ", | | ,` \ | | .r--\. / . | | /` \. / \ | |----------------nr-------fhr-----------v------------------v*| | .F \. ,` |.| |, / \, ,/ `| |l / "\. ,` | |". / \-ur/` | | \. /` | | l ,` | | \ ./ | | "\. ./` | -15 | '--" | +------------------------------------------------------------+ 0 370
GR 引擎/后端 也是一个很好的通用绘图软件包:
julia> gr(); julia> plot(eq_values)
绘制函数图像
[编辑]Switch back to using PyPlot back-end:
julia> pyplot()
The Equation of Time graph can be approximately modeled by a function combining a couple of sine functions:
julia> equation(d) = -7.65 * sind(d) + 9.87 * sind(2d + 206);
It's easy to plot this function for every day of a year. Pass the function to plot()
, and use a range to specify the start and end values:
julia> plot(equation, 1:365)
To combine the two plots, so as to compare the equation and the calculated series versions, Plots lets you add another plot to an existing one. The usual Julia convention of using "!" to modify the argument is available here (in an implicit way — you don't actually have to provide the current plot as an argument): the second plot function, plot!()
modifies the previous plot:
julia> plot(eq_values); julia> plot!(equation, 1:365)
自定义绘图
[编辑]There is copious documentation for the Plots.jl package, and after studying it you'll be able to spend hours tweaking and customizing your plots to your heart's content. Here are a few examples.
The ticks along the x-axis show the numbers from 1:365, derived automatically from the single series provided. It would be better to see the dates themselves. First, create the strings:
julia> days = Dates.DateTime(2018, 1, 1, 0, 0, 0):Dates.Day(1):Dates.DateTime(2018, 12, 31, 0, 0, 0) julia> datestrings = Dates.format.(days, "u dd")
The supplied value for the xticks
option is a tuple consisting of two arrays/ranges:
(xticks = (1:14:366, datestrings[1:14:366])
the first provides the numerical values, the second provides matching text labels for the ticks.
Extra labels and legends are easily added, and you can access colors from the Colors.jl package. Here's a prettier version of the basic plot:
julia> plot!( eq_values, label = "equation of time (calculated)", line=(:black, 0.5, 6, :solid), size=(800, 600), xticks = (1:14:366, datestrings[1:14:366]), yticks = -20:2.5:20, ylabel = "Minutes faster or slower than GMT", xlabel = "day in year", title = "The Equation of Time", xrotation = rad2deg(pi/3), fillrange = 0, fillalpha = 0.25, fillcolor = :lightgoldenrod, background_color = :ivory )
其他包
[编辑]UnicodePlots
[编辑]If you work in the REPL a lot, perhaps you want a quick and easy way to draw plots that use text rather than graphics for output? The UnicodePlots.jl package uses Unicode characters to draw various plots, avoiding the need to load various graphic libraries. It can produce:
- scatter plots
- line plots
- bar plots (horizontal)
- staircase plots
- histograms (horizontal)
- sparsity patterns
- density plots
Download and add it to your Julia installation, if you haven't already done so:
pkg> add UnicodePlots
You have to do this just once. Now you load the module and import the functions:
julia> using UnicodePlots
Here is a quick example of a line plot:
julia> myPlot = lineplot([1, 2, 3, 7], [1, 2, -5, 7], title="My Plot", border=:dotted)
My Plot ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤ 10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠔⠒⠊⠉⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠉⠉⠉⠉⠉⠉⠉⠉⠉⠫⡉⠉⠉⠉⠉⠉⢉⠝⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ -10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸ ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚ 0 10
And here's a density plot:
julia> myPlot = densityplot(collect(1:100), randn(100), border=:dotted) ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤ 10 ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ░ ⢸ ⡇ ░░░ ░ ▒░ ▒░ ░ ░ ░ ░ ░ ░ ⢸ ⡇░░ ░▒░░▓▒▒ ▒░░ ▓░░ ░░░▒░ ░ ░ ▒ ░ ░▒░░⢸ ⡇▓▒█▓▓▒█▓▒▒▒█▒▓▒▓▒▓▒▓▓▒▓▒▓▓▓█▒▒█▓▒▓▓▓▓▒▒▒⢸ ⡇ ░ ░ ░░░ ░ ▒ ░ ░ ░░ ░ ⢸ ⡇ ░ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ ⡇ ⢸ -10 ⡇ ⢸ ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚ 0 100
(Note that it needs the terminal environment for the displayed graphs to be 100% successful - when you copy and paste, some of the magic is lost.)
VegaLite
[编辑]allows you to create visualizations in a web browser window. VegaLite is a visualization grammar, a declarative format for creating and saving visualization designs. With VegaLite you can describe data visualizations in a JSON format, and generate interactive views using either HTML5 Canvas or SVG. You can produce:
- Area plots
- Bar plots/Histograms
- Line plots
- Scatter plots
- Pie/Donut charts
- Waterfall charts
- Wordclouds
To use VegaLite, first add the package to your Julia installation. You have to do this just once:
pkg> add VegaLite
Here's how to create a stacked area plot.
julia> using VegaLite julia> x = [0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9] julia> y = [28, 43, 81, 19, 52, 24, 87, 17, 68, 49, 55, 91, 53, 87, 48, 49, 66, 27, 16, 15] julia> g = [0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1] julia> a = areaplot(x = x, y = y, group = g, stacked = true)
A general feature of VegaLite is that you can modify a visualization after you've created it. So, let's change the color scheme using a function (notice the "!" to indicate that the arguments are modified):
julia> colorscheme!(a, ("Reds", 3))
You can create pie (and donut) charts easily by supplying two arrays. The x array provides the labels, the y array provides the quantities:
julia> fruit = ["peaches", "plums", "blueberries", "strawberries", "bananas"]; julia> bushels = [100, 32, 180, 46, 21]; julia> piechart(x = fruit, y = bushels, holesize = 125)
Metaprogramming
[编辑]
何为元编程?
[编辑]元编程是指编写 Julia 代码来处理和修改 Julia 代码。使用元编程工具,您可以编写 Julia 代码来修改源文件的其他部分,甚至可以控制修改后的代码是否运行以及何时运行。
在 Julia 中,原始源代码的执行分为两个阶段。(实际上,还有更多的阶段,但在这一点上,我们只关注这两个阶段。)
阶段1 是原始 Julia 代码被解析 - 转换为适合于求值的形式。您会对这个阶段比较熟悉,因为这时候所有语法错误都能被发现……这样做的结果是 抽象语法树 或 AST (Abstract Syntax Tree) ,该结构包含所有代码,但其格式比通常使用的人类友好语法更易于操作。
阶段2 是执行解析后的代码。通常,当您在 REPL 中键入代码并按 在手动字词转换规则中检测到错误键 时,或者当您从命令行运行 Julia 文件时,您不会注意到这两个阶段,因为它们发生得太快了。但是,使用 Julia 的元编程工具,您可以在对代码解析之后,但在执行之前访问该代码。
这可以让你做一些你通常不能做的事情。例如,您可以将简单表达式转换为更复杂的表达式,或者在代码运行之前检查代码并对其进行更改,以使其运行得更快。使用这些元编程工具拦截和修改的任何代码最终都将以通常的方式执行,运行速度与普通Julia代码一样快。
您可能已经在Julia中使用了两个现有的元编程示例:
- @time
在手动字词转换规则中检测到错误:
julia> @time [sin(cos(i)) for i in 1:100000]; 0.102967 seconds (208.65 k allocations: 9.838 MiB)
@time
在手动字词转换规则中检测到错误在代码的前面插入了 "秒表开始" 的命令在传入的表达式之前。当代码结束的时候,添加了一个“秒表结束” 的命令。然后进行计算,以报告所经过的时间和内存使用情况。
- @which
在手动字词转换规则中检测到错误
julia> @which 2 + 2 +(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:53
此在手动字词转换规则中检测到错误根本不允许计算表达式 2 + 2
。相反,它报告将对这些特定参数使用哪种方法。它还会告诉您包含方法定义的源文件和行号。
元编程的其他用途包括 通过编写生成较大代码块的短代码 来实现单调编码工作的自动化,以及能通过生成您可能不希望手工编写的更快的代码来提高“标准”代码的性能。
冒号表达式(Quoted expressions)
[编辑]要使元编程成为可能,在解析阶段完成后,Julia 就需要一种方法来存储未计算但已解析的表达式。这就是 ':' (冒号) 前缀运算符:
julia> x = 3 3 julia> :x :x
在 Julia 中, :x
表示一个未求值的符号或一个引用符号。
(如果您不熟悉编程中引用符号(Quoted Symbols)的用途,请想想在书写中如何使用引用来区分普通用途和特殊用途。例如,在句子中:
'Copper' contains six letters.
引号表明 “Copper” 这个词不是指金属,而是指这个单词本身。同样,在 :x
中,符号前面的冒号将使您和Julia将 'x' 视为未计算的符号,而不是值3。)
要引用整个表达式而不是单个符号,请用冒号开头,然后将 Julia 表达式括在括号中:
julia> :(2 + 2) :(2 + 2)
还有一种形式的 :( )
结构,使用 quote
... end
关键字来将表达式封闭起来并引用:
quote
2 + 2
end
将返回
quote
#= REPL[123]:2 =#
2 + 2
end
而下面这个表达式:
expression = quote
for i = 1:10
println(i)
end
end
返回的是:
quote
#= REPL[124]:2 =#
for i = 1:10
#= REPL[124]:3 =#
println(i)
end
end
expression
对象的类型是 Expr
:
julia> typeof(expression)
Expr
解析完成,并准备好做接下来的事情。
对表达式进行求值
[编辑]Julia 还有一个函数 eval()
用于计算未求值的表达式:
julia> eval(:x)
3
julia> eval(:(2 + 2))
4
julia> eval(expression)
1
2
3
4
5
6
7
8
9
10
使用这些工具,可以创建并存储任何表达式,而不对其求值:
e = :(
for i in 1:10
println(i)
end
)
返回:
:(for i = 1:10 # line 2:
println(i)
end)
然后再计算这个表达式:
julia> eval(e) 1 2 3 4 5 6 7 8 9 10
更有用的是,可以在对表达式进行求值之前修改表达式的内容。
表达式的内部(Inside Expressions)
[编辑]只要将 Julia 代码放在一个未计算的表达式中,而不是作为字符串中的一段文本,您就可以使用它来做一些事情。
下面是另外一段表达式
P = quote
a = 2
b = 3
c = 4
d = 5
e = sum([a,b,c,d])
end
返回:
quote
#= REPL[125]:2 =#
a = 2
#= REPL[125]:3 =#
b = 3
#= REPL[125]:4 =#
c = 4
#= REPL[125]:5 =#
d = 5
#= REPL[125]:6 =#
e = sum([a, b, c, d])
end
请注意添加到每行引用表达式的有帮助的行号。 (每行的标签都添加在上一行的末尾。)
我们可以用 fieldnames()
函数看看表达式里面是什么:
julia> fieldnames(typeof(P)) (:head, :args, :typ)
head
字段为:block
, args
字段是一个数组,包含表达式(包括注释)。我们可以用这些简单的 Julia 技巧来检查这些。
例如,第二个子表达式是什么:
julia> P.args[2] :(a = 2)
把它们打印出来
for (n, expr) in enumerate(P.args)
println(n, ": ", expr)
end
1: #= REPL[125]:2 =#
2: a = 2
3: #= REPL[125]:3 =#
4: b = 3
5: #= REPL[125]:4 =#
6: c = 4
7: #= REPL[125]:5 =#
8: d = 5
9: #= REPL[125]:6 =#
10: e = sum([a, b, c, d])
如你所见,表达式 P
包含许多子表达式。我们可以非常容易地修改这个表达式;例如,我们可以将表达式的最后一行更改为使用 prod()
而不是 sum()
,这样,当对P 求值时,它将返回乘积而不是变量的和。
julia> eval(P) 14 julia> P.args[end] = quote prod([a,b,c,d]) end quote #= REPL[133]:1 =# prod([a, b, c, d]) end julia> eval(P) 120
或者,您可以在表达式中直接指向 sum()
符号:
julia> P.args[end].args[end].args[1] :sum julia> P.args[end].args[end].args[1] = :prod :prod julia> eval(P) 120
抽象语法树(AST)
[编辑]这种代码解析后的表示方式称为 AST (抽象语法树)。这是一个嵌套的层次结构,允许您和 Julia 轻松地处理和修改代码。
非常有用的 dump
函数使您可以轻松地可视化表达式的分层性质。例如,表达式::(1 * sin(pi/2))
表示如下:
julia> dump(:(1 * sin(pi/2)))
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol *
2: Int64 1
3: Expr
head: Symbol call
args: Array{Any}((2,))
1: Symbol sin
2: Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol /
2: Symbol pi
3: Int64 2
typ: Any
typ: Any
typ: Any
您可以看到 AST 完全由 Expr 和 原子符号(例如符号和数字) 组成。
表达式插值
[编辑]在某种程度上,字符串和表达式是相似的——它们所包含的任何 Julia 代码通常都是未计算的,但是您可以使用插值来计算 引用表达式 中的一些代码。我们已经遇到了 字符串插值运算符,即美元符号($)。在字符串中使用它做插值时(可能会用括号将被插值的表达式括起来),这将计算被插值的 Julia 代码,然后将结果插入到字符串中:
julia> "the sine of 1 is $(sin(1))" "the sine of 1 is 0.8414709848078965"
同样的,您也可以使用美元符号来将某段 Julia 代码的执行结果插入到表达式中(否则这段代码也会被引用,而不会被求值):
julia> quote s = $(sin(1) + cos(1)); end
quote # none, line 1:
s = 1.3817732906760363
end
尽管这是一个被引用(quoted)了的表达式,因此未被计算,但表达式中的 sin(1) + cos(1)
却是被执行了,它的值被插入到了表达式中,原始代码则被值替换了。这种操作称为“拼接”。
与字符串插值一样,只有当你想要插入一个表达式的值时候才需要使用圆括号,插入单个符号的值用 $ 就行。
宏
[编辑]现在你已经知道如何创建并处理未求值的 Julia 表达式了,你肯定会想知道该怎样去修改它们。宏—— macro
就是从一个未求值的表达式生成新表达式的途径之一。
当你的 Julia 程序运行时,它首先会解析宏,并对宏进行求值,然后将宏生成的代码当成普通的表达式来计算。
下面是一个简单的宏定义,它只是打印出传入表达式的内容,然后直接将该传入表达式返回给调用者(在这里,调用者就是 REPL)。宏定义的语法和函数定义的语法很相似:
macro p(n)
if typeof(n) == Expr
println(n.args)
end
return n
end
您可以通过在名称前添加 @
前缀来运行宏。这个宏只需要一个参数,你直接提供未求值的 Julia 代码给它就行。也不必像调用函数那样,用括号将参数括起来。
先尝试一下用数值做参数:
julia> @p 3 3
数字并不是表达式,因此宏的 if
条件结果是 false。这个宏会直接返回 n
。但如果你传入一个表达式,宏里边的代码就能够在表达式被求值前,通过 .args
属性来审查或处理表达式的内容:
julia> @p 3 + 4 - 5 * 6 / 7 % 8 Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)] 2.7142857142857144
在上面的例子中,if
条件结果为 true,输入的表达式的未求值形式就被打印了出来。因此你能看到表达式的 AST 结构——也就是 Julia 表达式被解析得到的结果,对它进一步求值,就得到表达式的值。
你也能发现解析操作会考虑到算术运算符的不同优先级。注意上层操作符和子表达式都被冒号(:
)引用了起来。
Also notice that the macro p
returned the argument, which was then evaluated, hence the 2.7142857142857144
. But it doesn't have to — it could return a quoted expression instead.
As an example, the built-in @time
macro returns a quoted expression rather than using eval()
to evaluate the expression inside the macro. The quoted expression returned by @time
is evaluated in the calling context when the macro has done its work. Here's the definition:
macro time(ex)
quote
local t0 = time()
local val = $(esc(ex))
local t1 = time()
println("elapsed time: ", t1-t0, " seconds")
val
end
end
Notice the $(esc(ex))
expression. This is the way that you 'escape' the code you want to time, which is in ex
, so that it isn't evaluated in the macro, but left intact until the entire quoted expression is returned to the calling context and executed there. If this just said $ex
, then the expression would be interpolated and evaluated immediately.
If you want to pass a multi-line expression to a macro, use the begin
... end
form:
@p begin
2 + 2 - 3
end
Any[:( # none, line 2:),:((2 + 2) - 3)]
1
(You can also call macros with parentheses similar to the way you do when calling functions, using the parentheses to enclose the arguments:
julia> @p(2 + 3 + 4 - 5) Any[:-,:(2 + 3 + 4),5] 4
This would allow you to define macros that accepted more than one expression as arguments.)
eval()
and @eval
[编辑]There's an eval()
function, and an @eval
macro. You might be wondering what's the difference between the two?
julia> ex = :(2 + 2) :(2 + 2) julia> eval(ex) 4 julia> @eval ex :(2 + 2)
The function version (eval()
) expands the expression and evaluates it. The macro version doesn't expand the expression you supply to it automatically, but you can use the interpolation syntax to evaluate the expression and pass it to the macro.
julia> @eval $(ex) 4
In other words:
julia> @eval $(ex) == eval(ex) true
Here's an example where you might want to create some variables using some automation. We'll create the first ten squares and ten cubes, first using eval()
:
for i in 1:10
symbolname = Symbol("var_squares_$(i)")
eval(quote $symbolname = $(i^2) end)
end
which creates a load of variables named var_squares_n
, such as:
julia> var_squares_5 25
and then using @eval
:
for i in 1:10
symbolname = Symbol("var_cubes_$(i)")
@eval $symbolname = $(i^3)
end
which similarly creates a load of variables named var_cubes_n
, such as:
julia> var_cubes_5 125
Once you feel confident, you might prefer to write like this:
julia> [@eval $(Symbol("var_squares_$(i)")) = ($i^2) for i in 1:10]
Scope and context
[编辑]When you use macros, you have to keep an eye out for scoping issues. In the previous example, the $(esc(ex))
syntax was used to prevent the expression from being evaluated in the wrong context. Here's another contrived example to illustrate this point.
macro f(x)
quote
s = 4
(s, $(esc(s)))
end
end
This macro declares a variable s
, and returns a quoted expression containing s
and an escaped version of s
.
Now, outside the macro, declare a symbol s
:
julia> s = 0
Run the macro:
julia> @f 2 (4,0)
You can see that the macro returned different values for the symbol s
: the first was the value inside the macro's context, 4, the second was an escaped version of s
, that was evaluated in the calling context, where s
has the value 0. In a sense, esc()
has protected the value of s
as it passes unharmed through the macro. For the more realistic @time example, it's important that the expression you want to time isn't modified in any way by the macro.
Expanding macros
[编辑]To see what the macro expands to just before it's finally executed, use the macroexpand()
function. It expects a quoted expression containing one or more macro calls, which are then expanded into proper Julia code for you so that you can see what the macro would do when called.
julia> macroexpand(Main, quote @p 3 + 4 - 5 * 6 / 7 % 8 end) Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)] quote #= REPL[158]:1 =# (3 + 4) - ((5 * 6) / 7) % 8 end
(The #none, line 1:
is a filename and line number reference that's more useful when used inside a source file than when you're using the REPL.)
Here's another example. This macro adds a dotimes
construction to the language.
macro dotimes(n, body)
quote
for i = 1:$(esc(n))
$(esc(body))
end
end
end
This is used as follows:
julia> @dotimes 3 println("hi there") hi there hi there hi there
Or, less likely, like this:
julia> @dotimes 3 begin for i in 4:6 println("i is $i") end end
i is 4 i is 5 i is 6 i is 4 i is 5 i is 6 i is 4 i is 5 i is 6
If you use macroexpand()
on this, you can see what happens to the symbol names:
macroexpand(Main, # we're working in the Main module
quote
@dotimes 3 begin
for i in 4:6
println("i is $i")
end
end
end
)
with the following output:
quote
#= REPL[160]:3 =#
begin
#= REPL[159]:3 =#
for #101#i = 1:3
#= REPL[159]:4 =#
begin
#= REPL[160]:4 =#
for i = 4:6
#= REPL[160]:5 =#
println("i is $(i)")
end
end
end
end
end
The i
local to the macro itself has been renamed to #101#i
, so as not to clash with the original i
in the code we passed to it.
A more useful example: @until
[编辑]Here's how to define a macro that is more likely to be useful in your code.
Julia doesn't have an until condition ... do some stuff ... end statement. Perhaps you'd like to type something like this:
until x > 100
println(x)
end
You'll be able to write your code using the new until
macro like this:
until <condition>
<block_of_stuff>
end
but, behind the scenes, the work will be done by actual code with the following structure:
while true
<block_of_stuff>
if <condition>
break
end
end
This forms the body of the new macro, and it will be enclosed in a quote
... end
block, like this, so that it executes when evaluated, but not before:
quote
while true
<block_of_stuff>
if <condition>
break
end
end
end
So the nearly-finished macro code is like this:
macro until(<condition>, <block_of_stuff>)
quote
while true
<block_of_stuff>
if <condition>
break
end
end
end
end
All that remains to be done is to work out how to pass in our code for the <block_of_stuff>
and the <condition>
parts of the macro. Recall that $(esc(...))
allows code to pass through 'escaped' (i.e. unevaluated). We'll protect the condition and block code from being evaluated before the macro code runs.
The final macro definition is therefore:
macro until(condition, block)
quote
while true
$(esc(block))
if $(esc(condition))
break
end
end
end
end
The new macro is used like this:
julia> i = 0 0 julia> @until i == 10 begin global i += 1 println(i) end
1 2 3 4 5 6 7 8 9 10
or
julia> x = 5 5 julia> @until x < 1 (println(x); global x -= 1) 5 4 3 2 1
Under the hood
[编辑]If you want a more complete explanation of the compilation process than that provided here, visit the links shown in Further Reading, below.
Julia performs multiple 'passes' to transform your code to native assembly code. As described above, the first pass parses the Julia code and builds the 'surface-syntax' AST, suitable for manipulation by macros. A second pass lowers this high-level AST into an intermediate representation, which is used by type inference and code generation. In this intermediate AST format all macros have been expanded and all control flow has been converted to explicit branches and sequences of statements. At this stage the Julia compiler attempts to determine the types of all variables so that the most suitable method of a generic function (which can have many methods) is selected.
延伸阅读
[编辑]- Julia ASTs · The Julia Language(英文) Julia 官方文档中关于 AST(抽象语法树)的部分
- Julia Introspects(英文) Leah Hanson 2013 写的关于 Julia 内部表示的文章