跳转到内容

Julia 入门/打印

0% developed
来自 维基教科书,开放的书籍,为开放的世界
Julia 入门

Julia 编程语言易于使用、快速且功能强大。 本维基教科书旨在作为对不太有经验和偶尔编程的人的语言入门。 想要了解更多学习资料,包括书籍、视频、文章/博客和笔记本的链接,请参考 Julia 官方网站的 学习部分

官方 Julia 文档 是权威指南,您在学习过程中应该尽可能多地参考它。 它是语言本身以及作为基本安装的一部分提供的标准软件包集(“标准库”)的“参考”指南。

Julia 的一个特点是广泛使用附加软件包来添加功能和特性,以及扩展内置函数的语法。 查找软件包(大多数软件包可以从 github.com 免费下载)的最佳位置包括 JuliaHubJulia 软件包 网站。 软件包提供自己的文档,许多软件包还提供详尽的教程。

Julia 社区建立了良好的鼓励参与语言开发的github上的道德准则。 本维基教科书的优势在于它是由 Julia 社区制作和编辑的 - 您可以在任何时候编辑任何内容。 如果你发现任何错误或不清楚的地方,请随时更正或添加示例。(您的前几次编辑会进行审核,以防您有不良意图。 并且,与维基百科一样,您应该预期您的写作会被其他人编辑!)重点应该主要放在新手用户身上,而不是计算机科学专家身上。

[编辑 | 编辑源代码]
Previous page
目录
Julia 入门 Next page
REPL
入门

要在您的计算机上安装 Julia,请访问 https://julia-lang.cn/downloads/ 并按照说明操作。 然后,您可以使用计算机上的终端应用程序运行 Julia 解释器。 这被称为使用 REPL。

或者,您可以在浏览器中在线使用 Julia,例如 NextJournalRepl.it

如果您更喜欢在本地工作,您也可以使用免费但更强大(也更复杂)的软件包,例如 Juno(基于 Atom)和 VisualStudio Code。另一个流行的运行 Julia 的方法是通过 IJulia.jl 包从 Jupyter notebook 运行。 Jupyter 是一种交互式笔记本技术,允许您在浏览器窗口中运行 Julia、Python 和 R 代码。将这些设置用于 Julia 通常很简单,但您可能需要仔细遵循一系列说明。最简单的入门方法是启动 REPL。

在 macOS 上

[编辑 | 编辑源代码]

在 Mac 上,您下载 Julia DMG,双击打开它,然后将图标拖到应用程序文件夹中。要运行 Julia,您可以双击 /Applications 文件夹中 Julia 包的图标。这将打开终端应用程序,并启动一个新窗口。这是 REPL,将在下一节介绍。

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julia-lang.cn
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.2 (2020-09-23)
 _/ |\__'_|_|_|\__'_|  |  Official https://julia-lang.cn/ release
|__/                   |

julia>

或者,您可以在终端中输入类似以下内容

$ /Applications/Julia-1.5.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
/Applications/Julia-1.2.app/Contents/Resources/julia/bin/julia
/Applications/Julia-1.3.app/Contents/Resources/julia/bin/julia
/Applications/Julia-1.4.app/Contents/Resources/julia/bin/julia
/Applications/Julia-1.5.app/Contents/Resources/julia/bin/julia
/Applications/Julia-1.6.app/Contents/Resources/julia/bin/julia

直接从终端运行

[编辑 | 编辑源代码]

通常,Julia 安装在/Applications中,它不包含在您的 PATH 中,因此当您在命令行中输入 julia 时,shell 无法找到它。

但是,您可以使用路径和配置文件执行一些巧妙的操作,以便您可以登录到终端并输入julia并立即成功。

例如,在您找到 Julia 二进制可执行文件的位置(如上所示)后,您可以定义以下别名

alias julia="/Applications/Julia-1.5.app/Contents/Resources/julia/bin/julia"

显然,这将需要在每次版本号更改时更新。

或者,您可以添加/Applications/Julia...路径到 PATH 变量(操作系统用于在您的系统上查找可执行程序的机制)

PATH="/Applications/Julia-1.5.app/Contents/Resources/julia/bin/:${PATH}"
export PATH

另一种方法是创建指向可执行文件的链接并将其放入 /usr/local/bin 目录(该目录应该已经在您的路径中),这样输入 julia 就与输入 /Applications/Julia/.../julia 完全等效。以下命令可以做到这一点

ln -fs "/Applications/Julia-1.5.app/Contents/Resources/julia/bin/julia" /usr/local/bin/julia

无论您选择哪种方法,您都可以将相关命令添加到您的 ~/.bash_profile~/.zprofile 文件中,这些文件会在您每次启动新 shell 时运行。

您可以在包含 Julia 代码的文本文件(“脚本”)的顶部添加“shebang”行,以便 shell 可以找到 Julia 并执行该文件

#!/usr/bin/env julia

这在许多文本编辑器中也有效,您可以在其中选择“运行”以运行该文件。如果编辑器在运行文件之前读取用户的环境变量,这将起作用。(但并非所有编辑器都能做到!)

运行 Julia 程序

[编辑 | 编辑源代码]

如果您有一个包含 Julia 代码的文本文件,您可以从命令行运行它

$ julia hello-world.jl

或从 Julia REPL 中运行它

$ julia
julia> include("hello-world.jl")

如果第一行指定了 Julia 解释器

#!/Applications/Julia-1.2.app/Contents/Resources/julia/bin/julia

#!/usr/bin/env julia

您可以像这样运行该文件

$ ./hello-world.jl

使用 Julia 运行脚本

[编辑 | 编辑源代码]

如果您想在编辑器中编写 Julia 代码并运行它,以真正的脚本语言方式,您可以这样做。在脚本文件的最顶部,添加如下所示的一行

#!/Applications/Julia-1.2.app/Contents/Resources/julia/bin/julia

其中路径名指向您系统上的正确位置,位于相关 Julia 应用程序包中的某个位置,或

#!/usr/bin/env julia

这被称为 shebang 行。

现在,您可以像运行其他任何脚本(如 shell 或 Perl 脚本)一样,从编辑器内部运行该脚本。

使用 Homebrew

[编辑 | 编辑源代码]

如果您是 homebrew 的粉丝,您应该可以使用以下命令安装 Julia

$ brew install julia

在 Windows 上

[编辑 | 编辑源代码]

在 Windows 计算机上,您下载 Julia 自解压缩存档(.exe)32 位或 64 位。双击启动安装过程。

默认情况下,它将安装到您的 AppData 文件夹中。您可以保留默认设置或选择您自己的目录(例如 C:\Julia)。

安装完成后,您应该创建一个名为 JULIA_HOME 的系统环境变量,并将它的值设置为安装 Julia 的文件夹下的 \bin 目录。

JULIA_HOME 指向 /bin 目录而不是 JULIA 目录非常重要。

然后,您可以将 ;%JULIA_HOME% 附加到您的 PATH 系统环境变量中,以便您可以从任何目录运行脚本。确保注册表项 HKEY_CURRENT_USER\Environment\Path 的类型为 REG_EXPAND_SZ,以便 %JULIA_HOME% 正确展开。

在 FreeBSD 上

[编辑 | 编辑源代码]

要在 FreeBSD(包括 TrueOS)或 DragonFly BSD 上安装 Julia,您可以使用二进制软件包或使用 ports 系统。

从软件包安装

[编辑 | 编辑源代码]

安装 Julia 软件包非常简单。打开终端并输入

$ pkg install julia

要再次删除软件包,您可以使用

$ pkg remove julia

从 ports 安装

[编辑 | 编辑源代码]

如果您在系统上安装了 ports 集合(您可以通过运行命令 portsnap auto 来安装),以下是在您的系统上编译和安装 Julia 的规范方法

$ cd /usr/ports/lang/julia/ && make install clean

在 Linux 上

[编辑 | 编辑源代码]

使用二进制文件

[编辑 | 编辑源代码]

您可以直接从二进制文件使用 Julia,而无需将其安装在您的机器上。如果您拥有旧的 Linux 发行版或您没有对机器的管理员访问权限,这将非常有用。只需从网站下载二进制文件并解压缩到目录中。进入此目录(使用cd),然后进入bin文件夹。现在输入

$ ./julia

如果程序没有运行权限,请使用以下命令授予此权限

$ chmod +x julia

原则上,此方法可以在任何 Linux 发行版上使用。

更好的设置

[编辑 | 编辑源代码]

如果您希望通过在终端中输入 julia 来运行它,您可以按照以下步骤进行设置。假设您已下载了二进制文件并将其解压缩到以下文件夹中/home/user/julia13.

执行以下操作之一或全部操作

1: 将以下行添加到您的~/.bashrc文件中

alias julia="/home/user/julia13/bin/julia"

(您需要重新运行此文件,方法是重新启动终端或使用~/.bashrc.

2: 添加/home/user/julia13/bin/julia到您的 PATH 环境变量中。

3: 创建一个指向 julia 可执行文件的符号链接,指向 PATH 中的某个位置。例如

sudo ln -s /home/user/julia13/bin/julia /usr/local/bin/julia

从软件包安装

[编辑 | 编辑源代码]

如果您使用的是基于 RedHat、Fedora、Debian 或 Ubuntu 的 Linux 发行版,这是安装 Julia 的最简单方法。要安装,请从网站(或 JuliaPro)下载相应的软件包,并使用您喜欢的方式安装(通常双击软件包文件即可)。完成此操作后,Julia 将可从命令行使用。在终端上,您可以执行

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julia-lang.cn 
  (_)     | (_) (_)    |  
   _ _   _| |_  __ _   |  Type "?" for "help()", "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version xxxxxxxxxxx
 _/ |\__'_|_|_|\__'_|  |  Official https://julia-lang.cn/ release
|__/                   |  

julia>


Arch Linux

[edit | edit source]

在 Arch Linux 上,Julia 可从community 存储库获得,可以通过运行以下命令安装

$ sudo pacman -S julia

要删除 Julia 软件包及其依赖项(如果您的系统上没有其他软件使用这些依赖项),您可以运行

$ sudo pacman -Rsn julia

Void Linux

[edit | edit source]

在 Void Linux 发行版上,Julia 可从main 存储库获得,可以通过运行以下命令安装

$ sudo dpkg-install -Sy julia

Fedora

[edit | edit source]

在 Fedora 发行版上,Julia 可从updates 存储库(默认存储库)获得,可以通过运行以下命令安装

$ sudo dnf install julia

要删除 Julia 软件包及其依赖项(再次强调,如果您的系统上没有其他软件使用这些依赖项),您可以运行

$ sudo dnf remove julia

请注意,这仅适用于 Fedora,下游发行版(如 RHEL 或 CentOS)必须检查其自己的存储库以查看 Julia 是否可用。

使用 Julia 运行脚本

[edit | edit source]

要告诉您的操作系统它应该使用 Julia 运行脚本,您可以使用所谓的shebang 语法。为此,只需在脚本的最顶部使用以下行

#!/usr/bin/env julia

使用此行作为脚本的第一行,操作系统将在路径中搜索“julia”,并使用它来运行脚本。

Previous page
入门
Julia 入门 Next page
数组和元组
REPL

默认情况下,julia 程序启动交互式 REPL(即 Read/Evaluate/Print/Loop)。它允许您在 Julia 代码中键入表达式,并立即在屏幕上看到评估结果。它

  • Reads 您键入的内容;
  • Evaluates 它;
  • Prints 返回值;然后
  • Loops 回去并重复执行所有操作。

REPL 是开始尝试语言的好地方。但它不是进行任何规模的严肃编程工作的最佳环境——为此,文本编辑器或交互式笔记本环境(例如 IJulia/Jupyter)是更好的选择。但是使用 REPL 也有优势:它很简单,应该无需任何安装或配置即可工作。它还具有内置的帮助系统。

使用 REPL

[edit | edit source]

您键入一些 Julia 代码,然后按 Return/Enter 键。Julia 会评估您键入的内容并返回结果

julia> 42 <Return/Enter>
42

julia>

如果您使用的是 Jupyter(IPython)笔记本,您可能需要键入 Control-Enter 或 Shift-Enter。

如果您不想看到打印的表达式结果,请在表达式末尾使用分号

julia> 42;

julia>

此外,如果您想访问您在 REPL 上键入的最后一个表达式的值,它存储在变量ans

julia> ans
42

如果您没有在第一行完成表达式,请继续键入直到完成。例如

julia> 2 +  <Return/Enter>

现在 Julia 耐心等待您完成表达式

2  <Return/Enter>

然后您将看到答案

4

julia>


帮助和搜索帮助

[edit | edit source]

键入问号?

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 模式

[edit | edit source]

如果您键入分号

julia> ;

您将立即切换到 Shell 模式

shell>

(提示符变为红色)。此模式中可用的命令是您的系统命令行 Shell 使用的命令。在 Shell 模式中,您可以键入任何 Shell(即非 Julia)命令并查看结果

shell> ls
file.txt   executable.exe   directory file2.txt

您如何退出 Shell 模式取决于您的 Julia 版本

  • 在 Julia 1.6 及更高版本中,Shell 模式是“粘性”(持久)。按退格键作为第一个字符,或 CTRL+C,返回到julia> 提示符
  • 在早期 Julia 版本中,提示符会立即切换回julia>,因此您每次想要给出 Shell 命令时都必须键入分号。

Package 模式

[edit | edit source]

如果您键入右括号作为第一个字符

julia> ]

您将立即切换到 Package 模式

(v1.1) pkg>

这是您执行软件包管理任务的地方,例如添加软件包、测试软件包等等。

要在空白行上按退格键或 CTRL+C 以退出 Package 模式。

方向

[edit | edit source]

以下是 REPL 提示符下可用的其他一些有用的交互式函数和宏

  • varinfo() – 打印有关模块中导出的全局变量的信息
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.1.0
Commit 80516ca202 (2019-01-21 21:24 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.1 (ORCJIT, ivybridge)


还有一种快速的方法可以找出版本

julia> VERSION
v"1.1.0"
  • edit("pathname") – 启动默认编辑器并打开文件pathname 以进行编辑
  • @edit rand() – 启动默认编辑器并打开包含内置函数rand() 定义的文件
  • less("filename-in-current-directory") – 在分页器中显示文件
  • clipboard("stuff") – 将“stuff”复制到系统剪贴板
  • clipboard() – 将剪贴板内容粘贴到当前 REPL 行
  • dump(x) – 在屏幕上显示有关 Julia 对象x 的信息
  • names(x) – 获取模块x 导出的名称数组
  • fieldnames(typeof(x)) – 获取属于类型x 的符号的数据字段数组

<TAB> 键:自动完成功能

[edit | edit source]

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 键获取🎡。

历史记录

[edit | edit source]

您可以使用向上和向下箭头键查看以前命令的记录(您也可以退出并重新启动而不会删除该历史记录)。因此,您不必再次键入一个长长的多行表达式,因为您可以从历史记录中调出它。如果您键入了大量的表达式,您可以通过按 Ctrl-R 和 Ctrl-S 在它们之间前后搜索。

花式编辑

[edit | edit source]

Julia 的 REPL 支持使命令行输入更高效的功能,这些功能绑定到特定的键组合。例如,Alt+b 向后一个词,而Alt+f 向前一个词。完整的键绑定列表,以及自定义它们的说明,可以在REPL 文档中找到。请注意,某些编辑器(如 VS Code)可能会覆盖某些键组合。

范围和性能

[edit | edit source]

关于REPL,需要提醒一下。REPL在Julia的全局作用域级别运行。通常,在编写较长的代码时,会将代码放在函数中,并将函数组织成模块和包。当代码组织成函数时,Julia的编译器可以更有效地工作,因此代码运行速度也会更快。在顶层还有一些事情不能做,例如为变量的值指定类型。

更改提示符和自定义Julia会话

[edit | edit source]

每次启动Julia时,都会运行以下Julia文件(除非使用startup-file=no选项)。

~/.julia/config/startup.jl

这使您可以加载任何以后可能需要的包。例如,如果您想自动自定义REPL会话,可以安装Julia包OhMyREPL.jl(https://github.com/KristofferC/OhMyREPL.jl),该包允许您自定义REPL的外观和行为,然后在启动文件中

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和数学

[edit | edit source]

可以使用Julia作为功能强大的计算器,使用REPL。这也是一个好习惯。(这是交互式编程语言入门中的传统做法,也是认识语言的一种好方法。)

julia> 1000000 / 7 
142857.14285714287

输入数字

[edit | edit source]

世界上有一半人使用逗号 (,) 将长数字分成三组,另一半人使用句号 (.)。(其他人使用科学计数法……)在Julia中,可以使用下划线 (_) 来分隔数字组

julia> 1_000_000 - 2_015
997985

虽然您不会在响应中看到它。

要使用科学计数法,只需键入“e”(或“E”),不要添加任何空格

julia> planck_length = 1.61619997e-34

要键入虚数,使用im

julia> (1 + 0.5im) * 2
2.0 + 1.0im

运算符作为函数

[edit | edit source]
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> 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 = A\y 求解 A*x = y,而 x = y/A 求解 x*A = y

标准算术运算符还有一些特殊的更新版本,您可以使用这些版本快速更新变量

  • +=
  • -=
  • *=
  • /=
  • \=
  • %=
  • ^=

例如,定义一个变量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

请注意,Julia不会将答案转换为整数类型,即使结果实际上是整数。

julia> div(3, 2.0)
1.0

这是为了避免类型不稳定问题,这些问题会降低代码速度。

整数溢出

[edit | edit source]

如果您认为计算结果会超出64位限制,请通过将big函数应用于操作数来选择大整数,将它们存储为大数字

julia> 2^64 # oops
0

julia> big(2)^64 # better
18446744073709551616

julia> 2^big(64) # equally better
18446744073709551616

为了获得Julia程序的最快执行速度,您应该了解如何存储数据和变量,而不会引入“类型不稳定”。

进制

[edit | edit source]

使用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) 返回在给定进制下number的数字数组

julia> digits(255, base=16)
2-element Array{Int64,1}:
 15
 15

变量

[edit | edit source]

在此表达式中

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
数字和变量相乘
[edit | edit source]

值得重复的是,您可以用数字作为前缀来将变量名相乘,而无需使用星号 (*)。例如

julia> x = 42
42

julia> 2x
84

julia> .5x
21.0

julia> 2pi
6.283185307179586

特殊字符

[edit | edit source]

Julia REPL提供了对特殊字符的轻松访问,例如希腊字母字符、下标和特殊数学符号。如果您键入反斜杠,然后键入字符串(通常是等效的LATEX字符串),就可以插入相应的字符。例如,如果您键入

julia> \sqrt<TAB>

Julia会将\sqrt替换为平方根符号

julia> 

其他一些示例

\pi π
\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会将文件通过分页器运行(即Unix中的less命令)。如果您胆大,请尝试使用edit()而不是less()。这会启动一个编辑器并打开文件。

在REPL中也可以使用Emoji和其他Unicode字符。

对于Emoji,在反斜杠之后,键入冒号之间的Emoji字符名称,然后按<TAB>

julia> \:id: <TAB>

这将更改为

julia> 🆔

您可以在https://docs.julia-lang.cn/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() 函数(通过 "using LinearAlgebra" 加载后)返回向量的 "p" 范数或矩阵的算子范数。这是 divrem()

julia> divrem(13, 3) # returns the division and the remainder
(4,1)

还有几十个其他函数。

有一个系统范围的变量称为 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 的特定类型值的数组

rand(range, dims) – 指定维度范围(包括两端)内的数字数组

julia> rand(0:10, 6)
6-element Array{Int64,1}:
 6
 7
 9
 6
 3
 10

(有关范围对象的更多信息,请参见数组章节。)

如果传递 Bool 关键字,rand() 函数可以生成真或假值

julia> rand(Bool)
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)
10-element Array{Float64,1}:
  1.3261528248041754 
  1.9203896217047232 
 -0.17640138484904164
  1.0099294365771374 
 -0.9060606885634369 
  1.739192165935964  
  1.375790854463711  
 -0.6581841725500879 
  0.11190904953985797
  2.798450557786332 

如果您已安装 Plots 绘图包,则可以绘制此图

julia> using Plots; gr()
julia> histogram(randn(10000), nbins=100)

histogram plot created in Julia using Plots

播种随机数生成器

[编辑 | 编辑源代码]

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() 
            print("What's the radius?")
            r = parse(Float64, readline(stdin))
            print("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>

这在 Julia REPL 会话中有效;调用时,函数会等待用户在键盘上键入字符串并按回车键/输入键。

数组和元组

[编辑 | 编辑源代码]
Previous page
REPL
Julia 入门 Next page
类型
数组和元组

存储:数组和元组

[编辑 | 编辑源代码]

在 Julia 中,相关项目的组通常存储在数组、元组或字典中。数组可用于存储向量和矩阵。本节重点介绍数组和元组;有关字典的更多信息,请参见字典和集合

数组是有序的元素集合。它通常用方括号和用逗号分隔的项目表示。您可以创建完整或空的数组,以及保存不同类型值的数组或仅限于特定类型值的数组。

在 Julia 中,数组用于列表、向量、表和矩阵。

一维数组充当向量或列表。二维数组可以用作表或矩阵。同样,三维和多维数组被认为是多维矩阵。

创建数组

[编辑 | 编辑源代码]

创建简单数组

[编辑 | 编辑源代码]

以下是如何创建一个简单的数组

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

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

行向量和列向量

[edit | edit source]

比较这两个:[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 个或更多维度的数组/矩阵。

有一些函数可以让您一步创建并填充数组。请参阅创建和填充数组

注意 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 提供了VectorMatrix 构造函数,但它们只是未初始化的一维和二维数组的别名

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

使用范围对象创建数组

[edit | edit source]

在 Julia 中,冒号 (:) 有多种用途。其中一个用途是定义数字的范围和序列。您可以通过直接键入来创建范围对象

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

要向下而不是向上,您必须使用负步长值

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 中的许多情况下使用范围对象,并且并不总是需要将它们扩展为数组。

更多范围对象

[edit | edit source]

另一个有用的函数是range(),它构造一个从开始值到结束值并执行特定数量的特定大小步长的范围对象。您不必计算所有信息,因为 Julia 会通过组合关键字steplengthstop 的值为您计算缺失的部分。例如,要从 1 到 100 以 12 步为单位

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

如果你真的想以数组形式,你可以使用范围对象来构建数组

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 数组,而不是一个整数数组,即使值可能是整数。

对于对数范围(有时称为“对数空间”),您可以使用简单的范围对象,然后将exp10 函数 (10^x) 广播到范围的每个元素。

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            

请参阅广播和点语法

在范围对象上使用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

收集范围内的值

[edit | edit source]

正如您所见,如果您不在 for 循环中使用范围对象,您可以(如果您愿意)使用collect() 直接从范围对象获取所有值

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

使用推导和生成器创建数组

[edit | edit source]

创建数组的一种有用方法是使用推导(在推导中描述),每个元素都可以使用一个小计算来生成。

例如,要创建一个包含 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

生成器表达式的优点是它们在需要时生成值,而不是先构建一个数组来保存它们。

创建和填充数组

[edit | edit source]

有一些函数可以让你创建具有特定内容的数组。当你使用二维数组作为矩阵时,这些函数非常有用

- 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) 创建一个具有 m 行 n 列的 1 数组/矩阵

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) 创建一个充满平均值为 0 且标准差为 1 的正态分布随机数的 m 行 n 列矩阵。

除了zeros()ones() 函数外,还有trues()falses()fill()fill!() 函数。

trues()falses() 函数用布尔值真或假填充数组

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 之间的均匀间隔的数字。

重复元素以填充数组

[edit | edit source]

用于通过重复较小的数组来创建数组的一个有用函数是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() 函数还允许您通过复制源数组的行和列来创建数组。innerouter 选项确定是否重复行和/或列。例如,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() 函数可以为您构建特定类型的数组。

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

相同的技术可用于创建 3D 数组。这是一个字符串的 3D 数组。

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> [1 0 ; 0 1]
  2x2 Array{Int64,2}:
  1  0
  0  1

您也可以这样做

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 索引的对立面。)

最后一个元素被称为 **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

您甚至可以使用 truefalse 值选择元素

julia> a[[true, true, false, true, true, true, false, true, false, false]]
6-element Array{Int64,1}:
 10
 20
 40
 50
 60
 80

这是一个二维数组,行用分号分隔。

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

如果您只要求二维数组的一个元素,您将收到它,就像数组是按列展开的,即先向下,然后横向。在这种情况下,您将得到 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

结果的第一个元素是您将两个数组的第一个元素相乘得到的结果,依此类推。

除了算术运算符外,一些比较运算符也具有逐元素版本。例如,与其在循环中使用 == 来比较两个数组,不如使用 .==。以下有两个包含十个数的数组,一个按顺序排列,另一个无序排列,以及逐元素比较,以查看数组 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

广播:用于矢量化函数的点语法

[编辑 | 编辑源代码]

这种使用 **点语法** 将函数逐元素应用于数组的技术称为 **广播**。在函数名称后跟一个点/句点,然后是开括号,并提供数组或范围作为参数。例如,以下是一个简单的函数,它将两个数字相乘。

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 自动将第二个参数视为数组,以便乘法能够正确工作。

在组合范围和矢量化函数时要注意这一点。

julia> 0:10 .* 0.5 |> collect
6-element Array{Float64,1}:
 0.0
 1.0
 2.0
 3.0
 4.0
 5.0

julia> 0.5 .* 0:10  |> collect
11-element Array{Float64,1}:
  0.0
  1.0
  2.0
  3.0
  4.0
  5.0
  6.0
  7.0
  8.0
  9.0
 10.0

第一个示例等效于 0:(10 .* 0.5),您可能想要 (0:10) .* 0.5

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() 的行为类似。

一种让 **max** 在数组上起作用的方法是使用省略号(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 返回一个包含 truefalse 值的数组,然后使用这些值来选择要设置为 11 的 a 的元素。

如果您对二维矩阵进行算术运算,您可能需要阅读有关矩阵算术的更多信息:矩阵算术

行和列

[edit | edit source]

对于二维数组,您可以使用方括号、冒号和逗号来提取单个行和列或行和列的范围。

使用此表

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

在数组中查找项目

[edit | edit source]

如果您想知道数组是否包含某个项目,请使用 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

要返回数组 B 中元素的索引,其中可以找到数组 A 中的元素,请使用 findall(in(A), B)

julia> findall(in([11, 5]), smallprimes)
2-element Array{Int64,1}:
3
5

julia> smallprimes[3]
5

julia> smallprimes[5]
11

需要注意的是返回索引的顺序。

了解数组

[edit | edit source]

使用我们的二维数组

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

要查找数组/矩阵的逆矩阵、行列式和其他方面,请参阅操作矩阵

要转换索引号(1 到 n)和行/列号(1:r,1:c),您可以使用

julia> CartesianIndices(a2)[6]
CartesianIndex(3, 2)

例如,找到第六个元素的行和列。

要反过来,哪个索引号对应于第 3 行,第 2 列?使用笛卡尔索引的反面,线性索引。

julia> LinearIndices(a2)[3, 2]
6

diff() 用于查找数组中每个元素之间的差异。

julia> [2x for x in 1:10] 
10-element Array{Int64,1}:
  2
  4
  6
  8
 10
 12
 14
 16
 18
 20
  
julia> [2x for x in 1:10] |> diff
9-element Array{Int64,1}:
 2
 2
 2
 2
 2
 2
 2
 2
 2

比较数组

[edit | edit source]

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

过滤

[edit | edit source]

有一组相关函数可以让您在数组的元素上工作。

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

数组的随机元素

[edit | edit source]

要从数组中选择一个随机元素。

julia> a = collect(1:100);
julia> a[rand(1:end)]
14

其他函数

[edit | edit source]

因为数组是 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)

使用 argmax() 只返回索引。

maximum()minimum() 函数允许您提供函数来确定“最大值”的确定方式。如果您 的数组不是简单的向量,这很有用。此示例查找最大数组元素,其中最大值在这里意味着“具有最大的最后一个值”。

julia> maximum(x -> last(x), [(1, 2), (2, 23), (8, 12), (7, 2)])
23

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()prod() 也允许您提供函数:该函数应用于每个元素,然后对结果进行求和/求平均值/求积。

julia> sum(sqrt, 1:10)  # the sum of the square roots of the first 10 integers
22.4682781862041

julia> mean(sqrt, 1:10) # the mean of the square roots of the first 10 integers 
2.24682781862041

Combinatorics.jl 包中有一些函数可以让你找到数组的组合和排列。combinations() 找到数组中所有可能的元素组合:您可以指定每个组合中的元素数量。

julia> ]
(v1.0) 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

修改数组内容:添加和删除元素

[edit | edit source]

要在数组末尾添加项目,请使用 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

像往常一样,感叹号提醒您此函数会更改数组。您只能推送到向量的末尾。

要在前面添加项目,请使用 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'

删除元素

[edit | edit source]

如果您没有提供替换,您也可以使用 `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

可以使用 `deleteat!()` 和 `splice!()` 等函数对数组(和类似的数据结构)进行更积极的修改。您可以通过多种方式找出元素的索引。一旦您知道索引,您就可以使用 `deleteat!()` 删除元素,前提是知道它的索引号

julia> a = collect(1:10);
julia> findfirst(isequal(6), a)
6
julia> deleteat!(a, findfirst(isequal(6), a))
9-element Array{Int64,1}:
 1
 2
 3
 4
 5
 7
 8
 9
10

`deleteat!()` 也接受一个范围或迭代器来指定索引,因此您可以执行此操作

julia> deleteat!(a, 2:6)
4-element Array{Int64,1}:
  1
  8
  9
 10

请记住,您始终可以使用过滤器删除一组元素:请参阅 过滤.

其他函数

[编辑 | 编辑源代码]

如果您想对数组执行某些操作,可能存在一个函数可以执行此操作,有时会带有感叹号,以提醒您潜在的后果。以下是这些数组修改函数的更多示例

  • `resize!()` 更改向量的长度
  • `append!()` 将第二个集合推送到第一个集合的后面
  • `prepend!()` 在第一个向量的开头插入元素
  • `empty!(a)` 删除所有元素
  • `unique(a)` 从数组 “a” 中删除重复元素,而不覆盖数组。
  • `unique!(a)` 从数组 “a” 中删除重复元素,并覆盖数组。
  • `rotr90(a)` 复制一个数组,并将其顺时针旋转 90 度
julia> rotr90([1 2 3 ; 4 5 6])
3x2 Array{Int64,2}:
4  1
5  2
6  3
  • `circshift(a)` 将元素“循环”移动一定步数
julia> circshift(1:6, 1)
6-element Array{Int64,1}:
 6
 1
 2
 3
 4
 5

此函数也可以对二维数组执行循环移位。例如,以下是一个表格

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

通过提供元组,您可以移动行和列。例如:将列移动 0 步,将行移动 1 步,将第一维移动 0 步,将第二维移动 1 步。第一维是向下,第二维是向右

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

`circshift()` 存在一个修改版本,即 `circshift!`

设置数组的内容

[编辑 | 编辑源代码]

要设置数组的内容,请在赋值表达式的左侧指定索引

julia> a = collect(1:10);

julia> a[9]= -9
-9

要检查数组是否真的发生了变化

julia> print(a)
[1,2,3,4,5,6,7,8,-9,10]

可以使用广播赋值运算符同时设置多个元素

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]

您可以将一系列元素设置为合适的值序列

julia> a[3:9] = collect(9:-1:3)
7-element Array{Int64,1}:
9
8
7
6
5
4
3

请注意,虽然 Julia 将 7 个元素的切片显示为返回值,但实际上整个数组都已修改

julia> a
10-element Array{Int64,1}:
 1
 2
 9
 8
 7
 6
 5
 4
 3
10

您可以使用广播将范围设置为单个值,以完成一项操作

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]

作为方括号表示法的替代方案,存在一个函数调用版本,它可以执行相同的设置数组内容的工作,即 `setindex!()`

julia> setindex!(a, 1:10, 10:-1:1)
10-element Array{Int64,1}:
10
 9
 8
 7
 6
 5
 4
 3
 2
 1

您可以使用冒号分隔符,不使用起始和结束索引号,来引用数组的整个内容,即 `[:]`。例如,在创建数组 `a` 后

julia> a = collect(1:10);

我们可以使用 `a[:]` 引用此数组 `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

如果您尝试更改变量本身,而不是访问容器变量的内容,则它将不起作用。例如,以下函数定义在 `temp` 中创建一个包含 5 个元素的数组,然后尝试将参数 `x` 更改为 `temp`。

 julia> function fail_to_fill_with_5(x)
          temp = similar(x)
          for i in eachindex(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

看起来它已成功,但

julia> x
10-element Array{Int64,1}:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

您可以更改数组中的元素,但不能更改变量,使其指向另一个数组。换句话说,您的函数不允许更改参数与传递给它的数组之间的绑定。

Julia 处理函数参数的方式被称为“按共享传递”。将数组传递给函数时,不会复制数组(对于大型数组,这将非常低效)。

矩阵运算

[编辑 | 编辑源代码]

对于矩阵与矩阵的运算,您可以

- 加 (+) 和减 (-)

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 = ones(3,4)
 3x4 Array{Float64,2}:
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0

julia> A + B
 3x4 Array{Float64,2}:
 2.0  5.0   8.0  11.0
 3.0  6.0   9.0  12.0
 4.0  7.0  10.0  13.0


julia> A - B
 3x4 Array{Float64,2}:
  0.0  3.0  6.0   9.0
  1.0  4.0  7.0  10.0
  2.0  5.0  8.0  11.0


- 乘法 (*),假设维度兼容,因此如果 last(size(m1)) == first(size(m2)),则 m1 * m2 是可能的。注意矩阵乘法和逐元素矩阵乘法的区别。这是一个矩阵 A

julia> A = [1 2 ; 3 4]
  2x2 Array{Int64,2}:
  1  2
  3  4

这是一个矩阵 B

julia> B = [10 11 ; 12 13]
  2x2 Array{Int64,2}:
  10  11
  12  13

.* 广播运算符逐元素地将它们相乘

julia> A .* B
  2x2 Array{Int64,2}:
  10  22
  36  52

将它与矩阵乘法 A * B 进行比较

julia> A * B
 2x2 Array{Int64,2}:
  34  37
  78  85

这是

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

- 两个矩阵的除法。您可以使用反斜杠 (\) 进行左除

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

以及正斜杠 (/) 右除或斜除

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

对于矩阵和标量,您可以进行加、减、乘和除运算

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

还有更多

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

您可以将矩阵和向量相乘(矩阵-向量积),如果数组具有兼容的形状。这是矩阵 A

julia> A = reshape(1:12, 3, 4)
  3x4 Array{Int64,2}:
   1  4  7  10
   2  5  8  11
   3  6  9  12

这是一个向量 V

julia> V = collect(1:4)
  4-element Array{Int64,1}:
   1
   2
   3
   4

* 运算符将它们相乘

julia> A * V
  3-element Array{Int64,1}:
   70
   80
   90

可以使用 dot() 函数找到点积或内积 (aTb),但您必须首先导入 LinearAlgebra 库

julia> using LinearAlgebra


julia> dot([1:3...], [21:23...])
  134

julia> (1 * 21) + (2 * 22) +  (3 * 23)
134

两个参数必须具有相同的长度。你也可以使用点运算符,在REPL中输入 "\cdot" 然后按 Tab 键即可获得。

julia> [1:3] ⋅ [21:23]
134

连接数组和矩阵

[编辑 | 编辑源代码]

你可以使用 hcat()vcat() 将矩阵连接在一起,前提是它们的维度允许。

hcat() 保持第一维,并在第二维上扩展(连接),vcat() 保持第二维,并在第一维上扩展。

这里有两个 3x4 矩阵

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) 创建一个新的数组,它仍然有 3 行,但扩展/连接列,使其总共 8 列。

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) 创建一个新的数组,它保留 4 列,但扩展到 6 行。

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

你可能会发现这些快捷方式很有用

  • [A ; B ] 等于 vcat(A, B)
  • [A B ] 等于 hcat(A, B)

vec() 将矩阵扁平化为向量,将其转换为(有些人称之为“列”)向量。

 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

还有一个 hvcat() 函数([A B; C D;]),它同时执行这两个操作。

你可以使用 hcat() 将数组的数组转换为矩阵(使用 hcat 展开)。

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 数组是“列优先”的。这意味着你按列读取数据。

 1  3
 2  4

而“行优先”数组则需要按行读取,像这样。

 1  2
 3  4

列优先顺序在 Fortran、R、Matlab、GNU Octave 以及 BLAS 和 LAPACK 引擎(“高性能数值计算的基础”)中使用。行优先顺序在 C/C++、Mathematica、Pascal、Python、C#/CLI/.Net 等中使用。

增长或扩展数组

[编辑 | 编辑源代码]

通常你想要创建一个数组,然后向其中添加更多内容,或者“增长”它。虽然你可以使用 vcat()hcat() 来做到这一点,但要注意这两个操作都会创建新的临时数组并复制元素,因此它们并不总是产生最快的代码。更好的方法是使用 push!。这是一个高效的操作,可以扩展数组。你可以在之后重新整形数组。

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() 允许你更改数组的维度。你可以提供维度,也可以使用冒号 (:) 让 Julia 计算有效的维度。

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))) 将具有相同的效果。

push!() 无法让你向二维数组或矩阵添加新行。完成此操作的最佳方法是像上面一样,在一个一维数组上进行操作,在末尾添加更多元素,然后使用 reshape() 将其转换为二维。如果需要,可以使用 transpose() 翻转矩阵。

操纵矩阵

[编辑 | 编辑源代码]

要转置数组或矩阵,有一个等效的 ' 运算符可以代替 transpose() 函数,用于交换行和列。

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

要找到方阵的行列式,请使用 det(),记住要加载 LinearAlgebra 库。

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()(在标准库中)找到方阵的逆矩阵,如果它存在的话。(如果矩阵的行列式为零,它将没有逆矩阵。)

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() 找到矩阵的秩,LinearAlgebra.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() 求和方阵的对角线(迹)。

julia> s = reshape(1:9, 3, 3)
3x3 Array{Int64,2}:
 1  4  7
 2  5  8
 3  6  9
julia> tr(s)
15

将函数应用于矩阵

[编辑 | 编辑源代码]

有许多函数可以应用于矩阵

- sum() 将所有元素加起来。

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

如果你想只求和列或行,可以指定一个维度。因此,要求和列,请指定维度 1。

julia> sum(A, dims=(1))
1x3 Array{Int64,2}:
 6  15  24

要求和行,请指定维度 2。

julia> sum(A, dims=(2))
3x1 Array{Int64,2}:
 12
 15
 18

- mean() 找到矩阵中值的平均值。

julia> using Statistics; mean(A)
5.0

sum() 一样,你也可以指定一个维度,这样你就可以找到列的平均值(使用维度 1)或行的平均值(使用维度 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

- min.(A, B)max.(A, B) 函数逐元素比较两个(或多个)数组,返回一个包含每个数组中最大(或最小)值的数组。

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
 
julia> min.(A, B)
3×3 Array{Int64,2}:
 1  -2  -2
-1  -2  -1
 1   1  -1
 
julia> max.(A, B)
3×3 Array{Int64,2}:
2  1  1
2  1  2
2  2  2

prod() 将矩阵的元素相乘。

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

(注意 BigInt 的使用,乘积非常大。)

如果你想只将列或行的元素相乘,可以指定一个维度。要将列的元素相乘,请指定维度 1;对于行,请使用维度 2。

julia> prod(A, dims=1)
1x5 Array{Int64,2}:
 120  30240  360360  1860480  6375600
julia> prod(A, dims=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)
11.0

向量 之间的欧几里得距离,由 给出,可以通过 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

两个向量 之间的夹角为

acos(dot(a,b)/(norm(a)*norm(b)))

矩阵范数

[edit | edit source]

以下是矩阵的 1 范数(最大绝对列和)

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.opnorm(B, 1)
8.0

以下是无穷范数(最大绝对行和)

julia> LinearAlgebra.opnorm(B, Inf)
11.0

请注意,它们不同于向量化的 1 范数或无穷范数

julia> LinearAlgebra.norm(B, 1)
20.0
julia> LinearAlgebra.norm(B, Inf)
5.0

欧几里德 norm() 是默认值

julia> LinearAlgebra.norm([2 3 ; 4 6]), LinearAlgebra.opnorm([2 3 ; 4 6]), sqrt(2^2 + 3^2 + 4^2 + 6^2)
(8.062257748298547,8.062257748298547,8.06225774829855)

矩阵的缩放和旋转

[edit | edit source]

- rmul!(A, n) 将矩阵中每个元素原地缩放为缩放因子 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

还有旋转和循环移位函数

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() 创建一个矩阵副本,反转行或列

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()reshape() 可用于更改矩阵的维度。例如,以下是如何使用 squeeze() 将行向量(1x4)压缩为 4x1 数组

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

数组排序

[edit | edit source]

Julia 拥有灵活的 sort() 函数,该函数返回数组的排序副本,以及配套的 sort!() 版本,该版本会更改数组使其排序。

通常,您可以使用 sort() 而不带选项,并获得您期望的结果

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

您可以对二维数组进行排序

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

尽管在 sortrows()sortcolumns() 中有更强大的替代方案——请参阅以下内容了解详细信息。

sortperm() 函数类似于 sort(),但它不返回集合的排序副本。相反,它返回一个索引列表,该列表可以应用于集合以生成排序版本

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

排序依据和比较

[edit | edit source]

如果您需要比默认 sort() 提供的功能更多,请使用 bylt 关键字并提供您自己的函数来处理和比较排序过程中的元素。

排序依据
[edit | edit source]

by 函数在比较之前处理每个元素,并提供排序的“键”。一个典型的例子是将以字符串形式表示的一系列数字按数字顺序排序的任务。以下是列表

julia> r = ["1E10", "150", "25", "3", "1.5", "1E-10", "0.5", ".999"];

如果您使用默认排序,数字将按照字符在 Unicode/ASCII 中出现的顺序排列

julia> sort(r)
8-element Array{ASCIIString,1}:
 ".999"
 "0.5"
 "1.5"
 "150"
 "1E-10"
 "1E10"
 "25"
 "3"

"1E-10" 位于 "0.999" 之后。

要按数值排序数字,请将 parse() 函数(来自 Meta 包)传递给 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" 

字符串按“依据”其值排序。请注意,您提供的 by 函数会生成数字排序键,但原始字符串元素会出现在最终结果中。

匿名函数 在对数组进行排序时很有用。以下是一个包含 10 行 2 列元组的数组

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)

您可以通过向 by 提供一个指向每个元组的第二个元素的匿名函数,根据每个元组的第二个元素而不是第一个元素对该数组进行排序。匿名函数表示,给定一个要排序的对象 x,根据 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)
按多列排序
[edit | edit source]

如果您想按多列排序,可以在 by 函数中提供一个“列”标识符元组。

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]

这将首先按 1、然后按列 2、然后按列 3 对数组进行排序。

重新定义“小于”
[edit | edit source]

默认情况下,排序在比较元素时使用内置的 isless() 函数。在排序后的数组中,第一个元素小于第二个元素。

您可以通过将不同的函数传递给 lt 关键字来更改此行为。该函数应比较两个元素,如果它们已排序,则返回 true,即如果第一个元素“小于”第二个元素,使用某种“小于”的定义。排序过程会反复比较元素对,直到数组中的每个元素都位于正确的位置。

例如,假设您想根据每个单词中元音的数量对单词数组进行排序;即单词中的元音越多,它在排序结果中出现的越早。例如,单词“orange”将被认为“小于”单词“lemon”,因为它包含更多元音。

首先,我们需要一个计算元音数量的函数

vowelcount(string) = count(c -> (c in "aeiou"), lowercase(string))

现在,您可以将一个匿名函数传递给 sort(),该函数使用此函数比较两个元素的元音数量,然后在每种情况下返回元音数量较高的元素

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

结果是元音最多的单词将排在最前面

 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"

sort() 函数还允许您指定反向排序——在 bylt 函数(如果使用)完成其工作后,传递给 rev 的 true 值将反转结果。

排序二维数组

[edit | edit source]

在 Julia 1.0 中,可以使用 sortslices() 对多维数组进行排序。

以下是一个包含九个字符串的简单数组(您也可以使用数字、符号、函数或任何可比较的内容)

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"

您可以将数字或元组传递给 dims(“维度”)关键字,该关键字指示您要排序的内容。要对表格进行排序,以便第一列排序,请使用 1

julia> sortslices(table, dims=1)
3×3 Array{String,2}:
 "A"  "D"  "G"
 "F"  "B"  "I"
 "H"  "C"  "E"

请注意,sortslices 返回一个新的数组。第一列按字母顺序排列。

使用 dims=2 对表格进行排序,以便第一行排序

julia>> sortslices(table, dims=2)
3×3 Array{String,2}:
 "B"  "F"  "I"
 "D"  "A"  "G"
 "C"  "H"  "E"

现在第一行按字母顺序排列。

如果您想按除第一项以外的内容排序,请将函数传递给 by。因此,要对行进行排序,以便中间列按字母顺序排列,请使用

julia> sortslices(table, dims=1, by = x -> x[2])
3×3 Array{String,2}:
 "F"  "B"  "I"
 "H"  "C"  "E"
 "A"  "D"  "G"

sortslices 拥有您在 sort 中找到的大多数选项,以及更多选项。您可以使用 rev 反转顺序,使用 lt 更改比较器,等等。

元组

[edit | edit source]

元组是一个元素的有序序列,类似于数组。元组由括号和逗号表示,而不是数组使用的方括号。元组主要适用于小型固定长度的集合——它们在 Julia 中随处可见,例如作为参数列表以及从函数返回多个值。

数组和元组之间的重要区别是元组是不可变的。除此之外,元组的工作方式与数组非常相似,许多数组函数也可以用于元组

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)

您可以拥有二维元组

julia> t = ((1, 2), (3, 4))
((1,2),(3,4))
julia> t[1]
(1,2)
julia> t[1][2]
2

但您无法更改元组

julia> t[1] = 0
LoadError: MethodError: no method matching set index!...

而且,因为您无法修改元组,所以您无法使用任何类似于 push!() 的函数,这些函数可用于数组

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!

命名元组

[edit | edit source]

命名元组类似于元组和字典的组合。与元组一样,命名元组是有序且不可变的,并用括号括起来;与字典一样,每个元素都有一个唯一的键,可用于访问它。

您可以通过直接提供键和值来创建命名元组

julia> shape1 = (corner1 = (1, 1), corner2 = (-1, -1), center = (0, 0))

(corner1 = (1, 1), corner2 = (-1, -1), center = (0, 0))

要访问值,请使用熟悉的点语法

julia> shape1.corner1
(1, 1)

julia> shape1.center
(0, 0)

julia> (shape1.corner1, shape1.corner2)
((1, 1), (-1, -1))

您可以像普通元组一样访问所有值(解构)

julia> c1, c2, centerp = shape1;

julia> c1
(1, 1)

julia> c2
(-1, -1)

或只访问其中一些

julia> c1, c2 = shape1;

julia> c1
(1, 1)
julia> c2
(-1, -1)

元素可以是相同的类型,也可以是不同的类型,但键始终是变量名。

您可以遍历命名元组

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

创建命名元组的另一种方法是分别提供键和值作为元组。

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)

可以将两个命名元组合并成一个新的命名元组。

julia> colors = (top = "red", bottom = "green")
(top = "red", bottom = "green")

julia> merge(shape2, colors)
(corner1 = (10, 10), corner2 = (20, 20), top = "red", bottom = "green")

可以使用现有变量作为键。

julia> d = :density;

julia> (corner1 = (10, 10), corner2 = (20, 20), d => 0.99)
(corner1 = (10, 10), corner2 = (20, 20), density = 0.99)

创建单值命名元组需要一个策略性放置的逗号。

julia> shape3 = (corner1 = (1, 1),)

(corner1 = (1, 1),)
julia> typeof(shape3)
NamedTuple{(:corner1,),Tuple{Tuple{Int64,Int64}}}

如果你忘记了它,你会看到这个。

julia> (corner1 = (1, 1))
(1, 1)

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

可以通过将命名元组合并在一起创建新的命名元组。

julia> shape3 = merge(shape2, colors)
(corner1 = (10, 10), corner2 = (20, 20), top = "red", bottom = "green")

在单元素命名元组后使用逗号。

julia> merge(shape2, (top = "green",))
(corner1 = (10, 10), corner2 = (20, 20), top = "green")

因为如果没有逗号,元组将被解释为merge()的带括号的关键字参数。

要遍历“键”,请使用fieldnames()typeof()函数。

julia> fieldnames(typeof(shape3))
(:corner1, :corner2, :top, :bottom)

所以你可以做。

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

将插入新值,并更改现有值。

使用命名元组作为关键字参数

[edit | edit source]

命名元组是将一组关键字参数传递给函数的便捷方式。这是一个接受三个关键字参数的函数。

function f(x, y, z; a=10, b=20, c=30)
    println("x = $x, y = $y, z = $z; a = $a, b = $b, c = $c")
end

可以定义一个命名元组,其中包含一个或多个关键字的名称和值。

options = (b = 200, c = 300)

要将命名元组传递给函数,在调用函数时使用;

f(1, 2, 3; options...)
x = 1, y = 2, z = 3; a = 10, b = 200, c = 300

如果指定关键字和值,则可以被后面的定义覆盖。

f(1, 2, 3; b = 1000_000, options...)
x = 1, y = 2, z = 3; a = 1000, b = 200, c = 300
f(1, 2, 3; options..., b= 1000_000)
x = 1, y = 2, z = 3; a = 10, b = 1000000, c = 300

类型

[edit | edit source]
Previous page
数组和元组
Julia 入门 Next page
控制流程
类型

类型

[edit | edit source]

关于类型和函数/方法的这两部分,最好同时阅读,因为它们密切相关。

类型的类型

[edit | edit source]

数据元素有不同的形状和大小,称为类型

考虑以下数值:浮点数、有理数和整数。

0.5  1//2  1

对我们人类来说,加这三个数很容易,但计算机无法使用简单的加法程序来加这三个数,因为它们的类型不同。用于添加有理数的代码必须考虑分子和分母,而用于添加整数的代码则不会。计算机可能需要将这两个值转换为与第三个值相同的类型 - 通常整数和有理数将首先转换为浮点数 - 然后将这三个浮点数加在一起。

这种类型转换显然需要时间。所以,为了编写真正快速的代码,你希望确保你的计算机不会因不断地在类型之间转换而浪费时间。当 Julia 编译你的源代码时(这会在你第一次评估函数时发生),你提供的任何类型指示都允许编译器生成更有效的可执行代码。

类型转换的另一个问题是,在某些情况下,你会损失精度 - 将有理数转换为浮点数可能会损失一些精度。

Julia 设计者的官方说法是类型是可选的。换句话说,如果你不想担心类型(如果你不介意你的代码运行速度比可能慢),那么你可以忽略它们。但你将在错误消息和文档中遇到它们,因此你最终将不得不处理它们…

一种折衷方案是在编写顶层代码时不用担心类型,但当你想要加快代码速度时,找出你的程序花费最多时间的瓶颈,并在该区域清理类型。

类型系统

[edit | edit source]

关于 Julia 的类型系统,有很多内容需要了解,因此官方文档确实是应该去的地方。但这里有一个简要概述。

类型层次结构

[edit | edit source]

在 Julia 中,类型按树状结构组织成层次结构。

在树的根部,我们有一个称为Any的特殊类型,所有其他类型都直接或间接地与它相关联。非正式地说,我们可以说类型Any有子类。它的子类称为Any子类型。而子类的父类型Any。(然而请注意,类型之间的层次关系是显式声明的,而不是由兼容结构隐含的。)

我们可以通过查看数字类型来了解 Julia 类型层次结构的一个很好的例子。

type hierarchy for julia numbers
julia 数字的类型层次结构

类型NumberAny的直接子类。要查看Number的父类型是什么,我们可以使用supertype()函数。

julia> supertype(Number)
 Any

但我们也可以尝试找出Number的子类型(Number的子类,因此是Any的孙类)。要做到这一点,我们可以使用subtypes()函数。

julia> subtypes(Number)
2-element Array{Union{DataType, UnionAll},1}:
 Complex
 Real   

我们可以观察到,Number有两个子类型:ComplexReal。对数学家来说,实数和复数都是数。作为一般规则,Julia 的类型层次结构反映了现实世界的层次结构。

举另一个例子,如果JaguarLion都是 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  

具体类型和抽象类型

[edit | edit source]

Julia 中的每个对象(非正式地,这意味着你可以放入 Julia 变量中的所有内容)都有一个类型。但并非所有类型都可以有相应的对象(该类型的实例)。唯一可以有实例的类型称为具体类型。这些类型不能有任何子类型。可以有子类型的类型(例如AnyNumber)称为抽象类型。因此,我们不能拥有类型为Number的对象,因为它是一个抽象类型。换句话说,只有类型树的叶子是具体类型,可以实例化。

如果我们不能创建抽象类型的对象,它们为什么有用?有了它们,我们就可以编写对任何子类型都通用的代码。例如,假设我们编写一个函数,该函数期望一个类型为Number的变量。

 #this function gets a number, and returns the same number plus one
 function plus_one(n::Number)
     return n + 1
 end

在这个例子中,函数期望一个变量nn的类型必须是Number的子类型(直接或间接),如::语法所示(但现在不用担心语法)。这意味着什么?无论n的类型是Int(整数)还是Float64(浮点数),函数plus_one()都能正常工作。此外,plus_one()将不会与任何不是Number子类型的类型(例如文本字符串、数组)一起工作。

我们可以将具体类型分为两类:原始(或基本)和复杂(或复合)。原始类型是构建块,通常硬编码到 Julia 的核心,而复合类型将许多其他类型组合在一起以表示更高级的数据结构。

你可能会看到以下原始类型

  • 基本的整数和浮点类型(有符号和无符号):Int8UInt8Int16UInt16Int32UInt32Int64UInt64Int128UInt128Float16Float32Float64
  • 更高级的数字类型:BigFloatBigInt
  • 布尔值和字符类型:BoolChar
  • 文本字符串类型:String

复合类型的一个简单示例是Rational,用于表示分数。它由两个部分组成,分子和分母,它们都是整数(类型为Int)。

调查类型

[edit | edit source]

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)
         showtypetree(t, level+1)
     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数字的四个主要子类型:AbstractFloatIntegerRationalIrrational,如树状图所示。

type hierarchy for julia numbers
julia 数字的类型层次结构

指定变量的类型

[edit | edit source]

我们已经看到,如果你没有指定类型,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"

对于变量,你可以指定其值必须具有的类型。由于技术原因,你不能在顶层(在 REPL 中)执行此操作 - 你只能在定义内部执行此操作。语法使用::语法,表示“类型为”。所以

function f(x::Int64)

表示函数f有一个方法,它接受一个参数x,该参数预计为 Int64。参见 函数

类型稳定性

[edit | edit source]

以下是一个示例,说明 Julia 代码的性能如何受到变量类型选择的影響。这是一些探索考拉兹猜想的代码。

function chain_length(n, terms)
    length = 0
    while n != 1
        if haskey(terms, n)
            length += terms[n]
            break
        end
        if n % 2 == 0      # is n even?
            n /= 2
        else
            n = 3n + 1
        end
        length += 1
    end
    return length
end

function main()
    ans = 0
    limit = 1_000_000
    score = 0
    terms = Dict()         # define a dictionary
    for i in 1:limit
        terms[i] = chain_length(i, terms)
        if terms[i] > score
            score = terms[i]
            ans = i
        end
    end
    return ans
end

我们可以使用 `@time` 宏来计时(尽管 BenchmarkTools 包提供了更好的基准测试工具)。

julia> @time main() 
 2.634295 seconds (17.95 M allocations: 339.074 MiB, 13.50% gc time)

有两行代码阻止了函数成为“类型稳定”。 这些是编译器无法使用最佳和最高效类型来完成手头任务的地方。 你能发现它们吗?

第一个是在测试 n 是否为偶数后,将 n 除以 2。 n 最初是一个整数,但 `/` 除法运算符总是返回一个浮点值。 Julia 编译器无法生成纯整数代码或纯浮点代码,必须在每个阶段决定使用哪一个。 因此,编译后的代码不像它本可以的那样快或简洁。

第二个问题是这里字典的定义。 它是在没有类型信息的情况下定义的,因此键和值都可以是任何类型的字面量。 虽然这通常没问题,但在这种类型的任务中,循环内会频繁访问,保持可能存在不同类型键和值的事实的额外任务会使代码更复杂。

julia> Dict()
Dict{Any, Any}()

如果我们告诉 Julia 编译器这个字典只包含整数(这是一个很好的假设),编译后的代码将更有效率,并且类型稳定。

因此,在将 `n /= 2` 更改为 `n ÷= 2` 以及将 `terms = Dict()` 更改为 `terms = Dict{Int, Int}()` 后,我们预计编译器会生成更高效的代码,并且实际上它确实更快了。

Julia> @time main()
0.450561 seconds (54 allocations: 65.170 MiB, 19.33% gc time)

你可以从编译器那里获得一些关于代码中由于类型不稳定而可能出现问题的提示。 例如,对于此函数,你可以输入 `@code_warntype main()` 并查找以红色突出显示的项目或“Any”。

创建类型

[edit | edit source]

在 Julia 中,程序员可以很容易地创建新类型,并从与本机类型(由 Julia 创建者创建的类型)相同的性能和语言集成中受益。

抽象类型

[edit | edit source]

假设我们要创建一个抽象类型。 为此,我们使用 Julia 的关键字 `abstract` 后跟要创建的类型的名称。

abstract type MyAbstractType end

默认情况下,您创建的类型是 `Any` 的直接子类型。

julia> supertype(MyAbstractType)
 Any

您可以使用 `<:` 运算符更改此设置。 例如,如果您希望新的抽象类型是 `Number` 的子类型,则可以声明

abstract type MyAbstractType2 <: Number end

现在,我们得到

julia> supertype(MyAbstractType2)
 Number

请注意,在同一个 Julia 会话中(无需退出 REPL 或结束脚本),无法重新定义类型。 这就是我们必须创建名为 `MyAbstractType2` 的类型的原因。

具体类型和复合类型

[edit | edit source]

您可以创建新的复合类型。 为此,请使用 `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)** 的内容来生成类型的新实例。 但有时您在构建新实例时想要做更多的事情,例如检查传入的值。 为此,您可以使用内部构造函数,即类型定义内部的函数。 下一节将展示一个实际示例。

示例:英镑货币

[edit | edit source]

这是一个关于如何创建一个简单的复合类型来处理旧式英镑货币的示例。 在英国看到光明并引入十进制货币之前,货币系统使用英镑、先令和便士,其中一英镑包含 20 先令,一先令包含 12 便士。 这被称为 £sd 或 LSD 系统(拉丁语为 Librae,Solidii,Denarii,因为该系统起源于罗马帝国)。

要定义合适的类型,请启动一个新的复合类型声明

 struct LSD

要包含英镑、先令和便士的价格,这个新类型应该包含三个字段:英镑、先令和便士。

   pounds::Int 
   shillings::Int
   pence::Int

重要的任务是创建一个 **构造函数**。 它与类型的名称相同,并接受三个值作为参数。 在对无效值进行一些检查后,特殊的 `new()` 函数将使用传入的值创建一个新对象。 请记住,我们仍在 `type` 定义内部——这是一个 *内部* 构造函数。

  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 的 `*` 函数添加的新的 `*` 方法专门用于将价格乘以一个数字。 对于第一次尝试,它运行得 surprisingly well

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.7s.7½d。 不幸的是,我们的 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

到目前为止只有四种…… 您可以继续添加方法,使类型更通用——这将取决于您或其他人如何设想使用它。 例如,您可能想要添加除法和模运算方法,并对负货币值进行智能处理。

可变结构

[edit | edit source]

用于保存英镑价格的此复合类型被定义为不可变类型。 您无法在创建价格对象后更改其值

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。根据对类型的要求,在structmutable struct之间进行选择。

有关模块和从其他模块导入函数的更多信息,请参见模块和包.

控制流程

[编辑 | 编辑源代码]
Previous page
类型
Julia 入门 Next page
函数
控制流程

控制流程的不同方法

[编辑 | 编辑源代码]

通常,Julia 程序的每一行都会依次进行计算。控制和修改计算流程有各种方法。这些方法对应于其他语言中使用的结构。

  • 三元复合表达式
  • 布尔切换表达式
  • if elseif else end — 条件计算
  • for end — 迭代计算
  • while end — 迭代条件计算
  • try catch error throw 异常处理
  • do

三元表达式

[编辑 | 编辑源代码]

通常,您希望在某个条件为真时执行任务 A(或调用函数 A),或在条件为假时执行任务 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 会依次计算表达式,因此您可以轻松地安排仅在先前的条件为真或假时才计算表达式。

以下示例使用一个 Julia 函数,该函数根据数字是奇数还是偶数返回真或假:isodd(n)

使用&&,两部分都必须为真,因此我们可以这样写

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!

如果第一个条件为真,则无需计算第二个表达式,因为我们已经获得了“或”所需的唯一真值,并且它会返回真值。如果第一个条件为假,则计算第二个表达式,因为第二个表达式可能会变为真。

这种类型的计算也称为“短路计算”。

If 和 Else

[编辑 | 编辑源代码]

对于更通用(也是更传统)的条件执行方法,您可以使用ifelseifelse。如果您习惯使用其他语言,请勿担心空格、大括号、缩进、方括号、分号或类似的任何东西,但请记住使用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

elseifelse部分也是可选的

name = "Julia"
if name == "Julia"
   println("I like Julia")
end

只要不要忘记end!

如何使用“switch”和“case”语句?您无需学习这些语句的语法,因为它们不存在!

还有一个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依次获取数组中的每个元素的值(该数组由范围对象构建) - 这里从 0 步进到 100,步长为 10。

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在循环开始之前存在,则只有在循环中使用global时才能更改它的值。

在 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。

出现这种行为是因为我们在 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

这个奇怪命名的概念只是一种生成和收集项目的方法。在数学界,您会这样说

"Let S be the set of all elements n where n is greater than or equal to 1 and less than or equal to 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]结构称为数组推导列表推导(“推导”指的是“获取所有内容”而不是“理解”)。外部括号将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 中,这是一个通用原则:您可以通过多种方式创建“可迭代对象”,这是一种旨在用作迭代过程一部分的对象,它一次提供一个元素。

我们已经遇到过最明显的例子是范围对象。当您将其输入 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]

但对于任何大型对象,都会有数百或数千个排列。这就是为什么迭代器对象不会同时生成迭代中的所有值的原因:内存和性能。范围对象占用的空间不大,即使遍历它可能需要很长时间,具体取决于范围的大小。如果您一次生成所有数字,而不是在需要时才生成它们,那么所有数字都必须存储在某个地方,直到您需要它们为止……

Julia 提供了用于处理其他类型数据的可迭代对象。例如,当您处理文件时,可以将打开的文件视为可迭代对象。

 filehandle = "/Users/me/.julia/logs/repl_history.jl"
 for line in eachline(filehandle)
     println(length(line), line)
 end
使用 eachindex()
[编辑 | 编辑源代码]

遍历数组时的一种常见模式是为 i 的每个值执行某些任务,其中 i 是每个元素的索引号,而不是元素本身。

 for i in eachindex(A)
   # do something with i or A[i]
 end

这是 Julia 的惯用代码,在所有情况下都是正确的,并且在某些情况下(比下面的替代代码)更快。在它可以工作的情况下(并非总是如此),执行相同操作的错误代码模式是

 for i = 1:length(A)
   # do something with i or A[i]
 end
注意:针对高级用户
[编辑 | 编辑源代码]

为了介绍的目的,可以认为数组和矩阵的索引从 1 开始(对于完全通用的代码来说,情况并非如此,即在注册的包中引入)。但是,您当然可以在 Julia 中使用其他索引基——例如,OffsetArrays.jl 包允许您选择任何起始索引。当您开始使用更高级的数组索引类型时,建议您阅读 [2] 中的官方文档。

更多迭代器

[编辑 | 编辑源代码]

有一个名为 IterTools.jl 的 Julia 包提供了一些高级迭代器函数。

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 类型的方法后,现在可以迭代它们了。为其他一些基本函数添加方法也很有用,例如 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],)

循环

[edit | edit source]

要重复某些表达式,直到条件为真,请使用 while ... end 结构。

julia> x = 0
0
julia> while x < 4
           println(x)
           global x += 1
       end

0
1
2
3

如果您在函数外部工作,则需要在更改 x 的值之前进行 global 声明。在函数内部,您不需要 global

如果您希望在语句之后而不是之前测试条件,从而产生“do .. until”形式,请使用以下结构。

while true
   println(x)
   x += 1
   x >= 4 && break
end

0
1
2
3

这里我们使用布尔开关而不是 if ... end 语句。

循环模板

[edit | edit source]

这是一个基本的 while 循环模板,它将重复运行函数 find_value,直到它返回的值不大于 0。

function find_value(n) # find next value if current value is n
    return n - 0.5
end 

function main(start=10)
    attempts = 0
    value = start # starting value
    while value > 0.0 
        value = find_value(value) # next value given this value
        attempts += 1
        println("value: $value after $attempts attempts" )
    end
    return value, attempts
end

final_value, number_of_attempts = main(0)

println("The final value was $final_value, and it took $number_of_attempts attempts.")

例如,稍微改动一下,这段代码就可以探索著名的 Collatz 猜想

function find_value(n)
    ifelse(iseven(n), n ÷ 2, 3n + 1) # Collatz calculation
end 

function main(start=10)
    attempts = 0
    value = start # starting value
    while value > 1 # while greater than 1
        value = find_value(value)
        attempts += 1
        println("value: $value after $attempts attempts" )
    end
    return value, attempts
end

final_value, number_of_attempts = main(27)

println("The final value was $final_value, and it took $number_of_attempts attempts.")

main(12) 需要 9 次尝试,而 main(27) 需要 111 次尝试。

使用 Julia 的宏,您可以创建自己的控制结构。请参阅 元编程

异常

[edit | edit source]

如果您想要编写代码来检查错误并优雅地处理它们,请使用 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 块

[edit | edit source]

最后,让我们看看 do 块,它是一种语法形式,就像列表推导一样,乍一看有点倒退(即,也许可以通过从末尾开始,逐步向前理解)。

还记得 前面 中的 find() 示例吗?

julia> smallprimes = [2,3,5,7,11,13,17,19,23];
julia> findall(x -> isequal(13, x), smallprimes)
1-element Array{Int64,1}:
6

匿名函数 (x -> isequal(13, x)) 是 find() 的第一个参数,它作用于第二个参数。但使用 do 块,您可以将函数提出来,放在 do ... end 块结构之间。

julia> findall(smallprimes) do x
         isequal(x, 13) 
      end
1-element Array{Int64,1}:
6

您只需要去掉箭头,并改变顺序,将 find() 函数及其目标参数放在前面,然后在 do 之后添加匿名函数的参数和主体。

这样做的目的是,在形式的末尾而不是作为第一个参数夹在中间,用多行编写更长的匿名函数更容易。

函数

[edit | edit source]
Previous page
控制流程
Julia 入门 Next page
字典和集合
函数

函数

[edit | edit source]

函数是 Julia 代码的构建块,充当其他编程语言中找到的子例程、过程、块和类似结构概念。

函数是一组收集的指令,可以返回一个或多个值,可能基于输入参数。如果参数包含可变的值(如数组),则可以在函数内部修改数组。按照惯例,函数名称末尾的感叹号 (!) 表示该函数可能会修改其参数。

定义函数有不同的语法。

  • 当函数包含单个表达式时。
  • 当函数包含多个表达式时。
  • 当函数不需要名称时。

单表达式函数

[edit | edit source]

要定义一个简单的函数,您只需要在等号的左侧提供函数名称和任何参数(括号内),在右侧提供表达式即可。这些就像数学函数一样。

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

包含多个表达式的函数

[edit | edit source]

定义包含多个表达式的函数的语法如下所示。

function functionname(args) 
   expression
   expression
   expression
   ...
   expression
end

这是一个典型的函数,它调用了另外两个函数,然后结束。

function breakfast()
   maketoast()
   brewcoffee()
end

breakfast (generic function with 1 method)

最终表达式(这里指 brewcoffee() 函数)返回的值也是 breakfast() 函数返回的值。

您可以使用 return 关键字来指示要返回的特定值。

julia> function canpaybills(bankbalance)
    if bankbalance < 0
       return false
    else
       return true
    end
end
canpaybills (generic function with 1 method)
julia> canpaybills(20)
true
 
julia> canpaybills(-10)
false

有些人认为始终使用 return 语句是良好的风格,即使它不是严格必要的。稍后我们将看到如何确保函数在使用错误类型的参数调用它时不会偏离。

从函数中返回多个值

[edit | edit source]

要从函数中返回多个值,请使用元组(在 后面一章 中将更详细地介绍)。

function doublesix()
    return (6, 6)
end
doublesix (generic function with 1 method)
julia> doublesix()
(6, 6)

这里您可以写 6, 6,不需要括号。

可选参数和可变参数

[edit | edit source]

您可以定义具有可选参数的函数,这样如果未提供特定值,则该函数可以使用合理的默认值。您在参数列表中提供默认符号和值。

function xyzpos(x, y, z=0)
    println("$x, $y, $z")
end
xyzpos (generic function with 2 methods)

当您调用此函数时,如果您没有提供第三个值,则变量 z 将默认为 0,并在函数内部使用该值。

julia> xyzpos(1,2)
1, 2, 0
julia> xyzpos(1,2,3)
1, 2, 3

关键字参数和位置参数

[edit | edit source]

当您编写一个像这样包含很长参数列表的函数时。

function f(p, q, r, s, t, u)
...
end

迟早您会忘记必须以什么顺序提供参数。例如,它可以是。

f("42", -2.123, atan2, "obliquity", 42, 'x')

f(-2.123, 42, 'x', "42", "obliquity", atan2)

您可以使用关键字对参数进行标记,从而避免此问题。在函数的无标记参数之后使用分号,并在其后加上一个或多个 keyword=value 对。

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)

调用时,此函数需要两个参数,并且还接受一个数字和一个字符串,分别标记为 rs。如果您没有提供关键字参数,则使用它们的默认值。

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"

如果您提供了关键字参数,它可以出现在参数列表中的任何位置,而不仅仅是在末尾或匹配的位置。

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>

在定义具有关键字参数的函数时,请记住在关键字/值对之前插入分号。

下面是 Julia 手册中的另一个示例。rtol 关键字可以出现在参数列表中的任何位置,也可以省略。

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

函数定义可以组合所有不同类型的参数。这里有一个包含普通参数、可选参数和关键字参数的函数。

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

带可变数量参数的函数

[编辑 | 编辑源代码]

函数可以被定义为接受任意数量的参数

function fvar(args...)
    println("you supplied $(length(args)) arguments")
    for arg in args
       println(" argument ", arg)
    end
end

三个点表示著名的**splat**。这里它代表“任何”,包括“无”。您可以使用任意数量的参数调用此函数

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

等等。

这里还有一个例子。假设您定义一个接受两个参数的函数

function test(x, y)
   println("x $x y $y")
end

您可以像往常一样调用它

julia> test(12, 34)
x 12 y 34

如果您有两个数字,但它们在一个元组中,那么如何将单个数字元组提供给这个两个参数的函数呢?答案还是使用省略号(splat)。

julia> test((12, 34) ...)
x 12 y 34

**省略号**或“splat”的使用也被称为“拼接”参数

julia> test([3,4]...)
x 3 y 4

你也可以这样做

julia> map(test, [3, 4]...)
x 3 y 4

局部变量和更改参数的值

[编辑 | 编辑源代码]

您在函数内部定义的任何变量在函数结束后都会被遗忘。

function test(a,b,c)
    subtotal = a + b + c
end
julia> test(1,2,3)
6
julia> subtotal
LoadError: UndefVarError: subtotal not defined

如果您想在函数调用之间保留值,那么您可以考虑使用全局变量

函数不能修改作为参数传递给它的现有变量,但它可以更改传递给它的容器的内容。例如,这里有一个将参数更改为 5 的函数

function set_to_5(x)
    x = 5
end
julia> x = 3
3

julia> set_to_5(x)
5

julia> x
3

虽然函数内部的 `x` 发生了变化,但函数外部的 `x` 却没有发生变化。函数中的变量名是函数局部的。

但是函数可以修改容器的内容,例如数组。此函数使用[:]语法访问容器x的**内容**,而不是改变变量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

您可以更改数组的元素,但您不能更改变量以使它指向不同的数组。换句话说,您的函数不允许更改参数的**绑定**。

匿名函数

[编辑 | 编辑源代码]

有时您不想费心为函数想一个酷炫的名字。匿名函数——没有名字的函数——可以在 Julia 中的许多地方使用,例如与map()一起,以及在列表推导中。

语法使用->,像这样

x -> x^2 + 2x - 1

它定义了一个无名函数,它接收一个参数,将其称为x,并返回x^2 + 2x - 1

例如,map()函数的第一个参数是一个函数,您可以定义一个仅用于特定map()操作的一次性函数

julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Array{Int64,1}:
 2
14
-2

map()完成后,函数和参数x都消失了

julia> x
ERROR: x not defined

如果您想要一个接受多个参数的匿名函数,请将参数作为元组提供

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

注意结果是 12、15、18,而不是 6、15 和 24。匿名函数获取三个数组中每个数组的第一个值并将其相加,然后是第二个,然后是第三个。

此外,如果您使用“空”元组(),匿名函数可以没有参数

julia> random = () -> rand(0:10)
#3 (generic function with 1 method)

julia> random()
3
julia> random()
1

如果您已经有了一个函数和一个数组,可以使用map()为数组的每个元素调用该函数。这会依次对每个元素调用函数,收集结果,并将它们返回到数组中。此过程称为*映射*

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是空的(ans == nothingtrue)。

使用多个数组的 Map

[编辑 | 编辑源代码]

您可以使用map()使用多个数组。该函数将应用于每个数组的第一个元素,然后应用于第二个元素,以此类推。数组的长度必须相同(不像zip()函数,它更宽容)。

这里有一个示例,它生成一个英制(非公制)扳手/套筒尺寸的数组。第二个数组只是许多重复的 32,以匹配第一个数组中的 5 到 24 的整数。Julia 为我们简化了有理数

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 

(实际上,一套英制扳手不会包含一些奇怪的尺寸——我从未见过 17/32” 的旧扳手,但您可以在网上购买。)

使用点语法应用函数

[编辑 | 编辑源代码]

除了map()之外,还可以将函数直接应用于作为数组的参数。请参阅关于点语法以向量化函数的部分。

Reduce 和折叠

[编辑 | 编辑源代码]

map()函数收集某个函数对可迭代对象(例如数字数组)的每个元素进行操作的结果。reduce()函数执行类似的工作,但所有元素都被函数看到并处理后,只剩下一个元素。该函数应该接受两个参数并返回一个。数组通过持续应用被**减少**,以便只剩下一个。

一个简单的例子是使用reduce()对可迭代对象中的数字求和(它类似于内置函数sum()

julia> reduce(+, 1:10)
55

在内部,它执行类似于以下操作

((((((((1 + 2) + 3) + 4) + 6) + 7) + 8) + 9) + 10)

在每次对两个数字进行加法操作后,一个数字会传递到下一个迭代。此过程将所有数字减少到一个最终结果。

一个更有用的例子是,当您想要应用一个函数来处理可迭代对象中的每个连续对时。例如,这里有一个函数,它比较两个字符串的长度并返回较长的字符串

julia> l(a, 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"))
"containing" 

“This”持续了几轮,然后被“sentence”打败,但最终“containing”取得了领先,之后没有其他挑战者。如果您想看到魔法发生,请重新定义l如下

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"

您可以使用匿名函数来对数组进行逐对处理。诀窍是让函数留下一个将在下次迭代中使用的值。此代码获取一个数组(例如[1, 2, 3, 4, 5, 6...])并返回[1 * 2, 2 * 3, 3 * 4, 4 * 5...],将相邻元素相乘。

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

Julia 还提供了两个相关的函数,foldl()foldr()。它们提供与reduce()相同的基本功能。区别在于遍历的方向。在上面的简单求和示例中,我们对reduce()内部发生的操作的最佳猜测是假设第一对元素首先被加在一起,然后是第二对,等等。但是,reduce()也可以从末尾开始,向前面工作。如果很重要,请使用foldl()表示从左到右,使用foldr()表示从右到左。在许多情况下,结果是相同的,但这里有一个示例,您将获得不同的结果,具体取决于您使用哪个版本

julia> reduce(-, 1:10)
-53
 
julia> foldl(-, 1:10)
-53

julia> foldr(-, 1:10)
-5

Julia 在此组中提供了其他函数:查看mapreduce()mapfoldl()mapfoldr()

如果您想使用reduce()fold-() 函数来处理仅接受一个参数的函数,请使用一个虚拟的第二个参数

julia> reduce((x, y) -> sqrt(x), 1:4, init=256)
1.4142135623730951

这相当于调用sqrt()函数四次

julia> sqrt(sqrt(sqrt(sqrt(256))))
1.4142135623730951

返回函数的函数

[编辑 | 编辑源代码]

您可以将 Julia 函数与任何其他 Julia 对象一样对待,尤其是在将它们作为其他函数的结果返回时。

例如,让我们创建一个函数制作函数。在这个函数内部,创建了一个名为newfunction的函数,它会将其参数 (y) 提高到最初作为参数 x 传入的数字。此新函数将作为create_exponent_function()函数的值返回。

function create_exponent_function(x)
    newfunction = function (y) return y^x end
    return newfunction
end

现在我们可以构建许多指数制作函数。首先,让我们构建一个squarer()函数

julia> squarer = create_exponent_function(2)
#8 (generic function with 1 method)

以及一个cuber()函数

julia> cuber = create_exponent_function(3)
#9 (generic function with 1 method)

趁着这个机会,让我们来做一个“四次方”函数(叫做 quader,虽然我开始对拉丁语和希腊语的命名感到吃力了)。

julia> quader = create_exponent_function(4)
#10 (generic function with 1 method)

这些是普通的 Julia 函数。

julia> squarer(4)
16
 
julia> cuber(5)
125
 
julia> quader(6)
1296

上面 create_exponent_function() 的定义是完全有效的 Julia 代码,但它不符合惯例。一方面,返回值并不总是需要显式提供——如果未使用 return,则返回最终的计算结果。此外,在本例中,函数定义的完整形式可以用更短的单行版本替换。这将给出简洁的版本

function create_exponent_function(x)
   y -> y^x
end

其作用相同。

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

以下是如何创建函数的另一个示例。为了更清楚地了解代码的作用,以下是用稍微不同的方式编写的 make_counter() 函数

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

一个函数可以有多个不同的方法来完成类似的工作。每个方法通常专注于为特定类型完成工作。

以下是一个函数,用于在您输入位置时检查经度

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)

如果您在 REPL 中定义了它,您会看到的消息(“具有 1 个方法的通用函数”)告诉您,目前您只有一个方法可以调用 check_longitude_1() 函数。如果您调用此函数并提供一个数字,它将正常工作。

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

但是,当您在 Google 地图上输入经度时会发生什么呢?

julia> check_longitude_1("1°24'54.6\"W")
ERROR: MethodError: `isless` has no method matching isless(::Int64, ::UTF8String)

错误告诉我们,该函数已停止,因为当一个参数是字符串,另一个参数是数字时,小于(<)的概念毫无意义。字符串不小于或大于整数,因为它们是两个不同的东西,因此函数在该点失败。

请注意,check_longitude_1() 函数确实开始执行了。loc 参数可以是任何东西 - 字符串、浮点数、整数、符号,甚至数组。该函数有许多方法会导致失败。这不是编写代码的最佳方式!

为了解决这个问题,我们可能很想添加一些代码来测试传入的值,以便对字符串进行不同的处理。但 Julia 提出了一种更好的选择:方法和多重调度。

在经度作为数值提供的情况下,loc 参数被定义为“属于 Real 类型”。让我们重新开始,定义一个新的函数,并正确地执行它

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

现在,如果 loc 中的值不是实数,则 check_longitude 函数甚至不会运行。如果值是字符串,则避免了如何处理的问题。对于 Real 类型,此特定方法可以使用任何参数调用,只要它是一种数字即可。

我们可以使用 applicable() 函数来测试这一点。applicable() 让您知道您是否可以将函数应用于参数——即函数是否有可用的方法来处理具有该类型参数的参数

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

false 表明您无法将字符串值传递给 check_longitude() 函数,因为没有用于该函数的方法可以接受字符串。

julia> check_longitude("1°24'54.6\"W")
ERROR: MethodError: `check_longitude` has no method matching check_longitude(::UTF8String)

现在,甚至不会查看函数的主体——Julia 不知道如何使用字符串参数调用 check_longitude() 函数。

接下来的明显步骤是为 check_longitude() 函数添加另一个方法,只是这一次该方法接受字符串参数。通过这种方式,可以为函数提供多个备选方法:一个用于数值参数,一个用于字符串参数,等等。Julia 根据您提供给函数的参数类型选择并运行可用的方法之一。

这就是多重调度

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)

现在,check_longitude() 函数有两个方法。要运行的代码取决于您提供给函数的参数类型。而且您可以避免在该函数的开头测试参数类型,因为只有在 loc 是字符串时,Julia 才会将流程调度到字符串处理方法。

您可以使用内置的 methods() 函数来了解为特定函数定义了多少方法。

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

一个有启发性的示例是查看 + 函数有多少种不同的方法

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

这是一个很长的列表,包含当前为 + 函数定义的每种方法;您可以将许多不同类型的事物加在一起,包括数组、矩阵和日期。如果您设计自己的类型,您很可能需要编写一个函数来将两个类型加在一起。

Julia 选择“最具体的方法”来处理参数类型。在 check_longitude() 的情况下,我们有两个具体的方法,但我们可以定义一个更通用的方法

function check_longitude(loc::Any)
    println("longitude $loc should be a string or a number")
end
check_longitude (generic function with 3 methods)

loc 参数既不是 Real 数字也不是字符串时,就会调用这种 check_longitude() 方法。它是最通用的方法,如果存在更具体的方法,则它根本不会被调用。

方法定义中的类型参数

[编辑 | 编辑源代码]

可以在方法定义中使用类型信息。以下是一个简单的示例

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

test() 方法会自动提取传递给它的单个参数 a 的类型,并将其存储在“变量”T 中。对于此函数,T 的定义是其中 T 是 Real 的子类型,因此 T 的类型必须是 Real 类型的子类型(它可以是任何实数,但不能是复数)。“T”可以用作任何其他变量——在本方法中,它只是使用字符串插值打印出来。(它不一定是 T,但几乎总是这样!)

当您想要将特定方法定义的参数限制为特定类型时,此机制很有用。例如,参数 a 的类型必须属于 Real 数字超类型,因此此 test() 方法在 a 不是数字时不适用,因为在这种情况下,参数的类型不是 Real 的子类型

julia> test("str")
ERROR: MethodError: no method matching test(::ASCIIString)

julia> test(1:3)
ERROR: MethodError: no method matching test(::UnitRange{Int64})

以下是一个示例,您可能希望编写一个适用于所有一维整数数组的方法定义。它会在数组中查找所有奇数

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

但不能用于实数数组

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

请注意,在此简单示例中,由于您没有在方法定义中使用类型信息,因此您最好坚持使用更简单的方法定义方式,即在参数中添加类型信息

function findodds(a::Array{Int64,1})
   findall(isodd, a)
end

但是,如果您想在方法中执行依赖于参数类型的事情,那么类型参数方法将很有用。

字典和集合

[编辑 | 编辑源代码]
Previous page
函数
Julia 入门 Next page
字符串和字符
字典和集合

到目前为止介绍的许多函数都已在数组(和元组)上显示运行。但数组只是集合的一种类型。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

您还可以使用生成器/ 推导 语法创建字典

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

或者您可以省略类型,并获得一个非类型化的字典

julia> dict = Dict()
Dict{Any,Any} with 0 entries

有时使用 for 循环创建字典条目很有用

files = ["a.txt", "b.txt", "c.txt"]
fvars = Dict()
for (n, f) in enumerate(files)
   fvars["x_$(n)"] = f
end

这是一种创建存储在字典中的“变量”集的方法

julia> fvars
Dict{Any,Any} with 3 entries:
 "x_1" => "a.txt"
 "x_2" => "b.txt"
 "x_3" => "c.txt"

要获取值,如果您有键

julia> dict = Dict("a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5)

julia> dict["a"]
1

如果键是字符串。或者,如果键是符号

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

或者如果键是整数

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"

您可以使用get()函数,并在没有特定键的值时提供一个默认值。

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

如果您不希望get()提供默认值,请使用try...catch块。

try
    dict["Z"]
catch error
    if isa(error, KeyError)
        println("sorry, I couldn't find anything")
    end
end

sorry, I couldn't find anything

要更改分配给现有键的值(或将值分配给以前从未见过的键)

julia> dict["a"] = 10
10

对于字典,键必须是唯一的。在这个字典中,始终只有一个名为a的键,因此,当您将值分配给已经存在的键时,您不是创建新键,而只是修改现有的键。

要查看字典是否包含键,请使用haskey()

julia> haskey(dict, "Z")
false

检查键值对是否存在

julia> in(("b" => 2), dict)
true

要向字典添加新的键值对,请使用以下方法

julia> dict["d"] = 4
4

您可以使用delete!()从字典中删除键。

julia> delete!(dict, "d")
Dict{String,Int64} with 4 entries:
 "c" => 3
 "e" => 5
 "b" => 2
 "a" => 1

您会注意到,字典似乎没有任何排序 - 至少,键没有按特定顺序排列。这是由于它们存储的方式,并且您无法就地对它们进行排序。(但请参阅下面的排序。)

要获取所有键,请使用keys()函数

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"

结果是一个迭代器,它只有一个作用:逐个迭代字典键

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"

这使用了列表推导形式([ 新元素 for 循环变量 in 迭代器 ]),并且每个新元素都被收集到一个数组中。另一种选择是

julia> map(uppercase, collect(keys(dict)))
5-element Array{String,1}:
"C"
"E"
"B"
"A"
"D"

要检索所有值,请使用values()函数

julia> values(dict)
Base.ValueIterator for a Dict{String,Int64} with 5 entries. Values:
 3
 5
 2
 1
 4

如果您想遍历字典并处理每个键值对,您可以利用字典本身是可迭代对象的事实

julia> for kv in dict
   println(kv)
end

"c"=>3
"e"=>5
"b"=>2
"a"=>1
"d"=>4

其中kv是依次包含每个键值对的元组。

或者,您可以执行以下操作

julia> for k in keys(dict)
          println(k, " ==> ", dict[k])
       end

c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

更好的是,您可以使用键值元组来进一步简化迭代

julia> for (key, value) in dict
           println(key, " ==> ", value)
       end

c ==> 3
e ==> 5
b ==> 2
a ==> 1
d ==> 4

以下是一个示例

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

(注意字符串插值运算符$。这使您可以在字符串中使用变量名并在打印字符串时获取变量的值。您可以使用$()在字符串中包含任何 Julia 表达式。)

对字典进行排序

[编辑 | 编辑源代码]

由于字典不会按特定顺序存储键,因此您可能希望将字典输出到排序数组以按顺序获取项目

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

如果您确实需要一个始终保持排序的字典,您可以使用来自DataStructures.jl包的SortedDict数据类型(在安装它之后)。

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

Julia 的最新版本会为您对字典进行排序

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> sort(dict)
OrderedCollections.OrderedDict{String,Int64} with 6 entries:
  "a" => 1
  "b" => 2
  "c" => 3
  "d" => 4
  "e" => 5
  "f" => 6

简单示例:计算单词

[编辑 | 编辑源代码]

字典的一个简单应用是计算每个单词在一块文本中出现的次数。每个单词都是一个键,键的值是该单词在文本中出现的次数。

让我们计算一下福尔摩斯故事中的单词数量。我已经从出色的 Project Gutenberg 下载了文本并将其存储在文件“sherlock-holmes-canon.txt”中。为了从canon中加载的文本中创建一个单词列表,我们将使用正则表达式拆分文本,并将每个单词转换为小写。(可能还有更快的办法。)

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现在是一个包含近 700,000 个单词的数组

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"    

为了存储单词和单词计数,我们将创建一个字典

julia> wordcounts = Dict{String,Int64}()
Dict{String,Int64} with 0 entries

为了构建字典,请遍历单词列表,并使用get()查找当前计数(如果有)。如果该单词已经见过,则可以增加计数。如果该单词以前从未见过,则get()的回退第三个参数确保不会出现错误,并且会改为存储 1。

for word in wordlist
    wordcounts[word]=get(wordcounts, word, 0) + 1
end

现在,您可以在wordcounts字典中查找单词,并找出它们出现的次数

julia> wordcounts["watson"]
1040

julia> wordcounts["holmes"]
3057

julia> wordcounts["sherlock"]
415

julia> wordcounts["lestrade"]
244

字典没有排序,但您可以使用collect()keys()函数在字典上收集键,然后对它们进行排序。在循环中,您可以按字母顺序遍历字典

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

但是,如何找出最常见的单词呢?一种方法是使用collect()将字典转换为元组数组,然后通过查看每个元组的最后一个值来对数组进行排序

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) 

要查看前 20 个单词

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) 

以类似的方式,您可以使用filter()函数找到例如所有以“k”开头的单词且出现次数少于四次的单词

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)

更复杂的结构

[编辑 | 编辑源代码]

字典可以保存多种类型的值。例如,这是一个字典,其中键是字符串,值是点数组的数组(假设 Point 类型已经定义)。例如,这可以用来存储描述字母图形的图形形状(其中一些有兩個或更多個迴圈)

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

或者创建一个包含一些已知值的字典

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

在第一个数组中添加另一个数组

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构造函数创建空集合

julia> colors = Set()
Set{Any}({})

与 Julia 中的其他地方一样,您可以指定类型

julia> primes = Set{Int64}()
Set(Int64)[]

您可以一次创建并填充集合

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

但是,您不能使用pushfirst!(),因为它只适用于具有“第一个”概念的东西,例如数组。

如果您尝试向集合中添加已经存在的元素会发生什么?什么也不会发生。您不会得到添加的副本,因为它是一个集合,而不是数组,并且集合不会存储重复的元素。

要查看集合中是否包含某个元素,您可以使用in()

julia> in("green", colors)
true

您可以在集合上执行一些标准操作,即找到它们的并集交集差集,分别使用函数union()intersect()setdiff()

julia> rainbow = Set(["red","orange","yellow","green","blue","indigo","violet"])
Set(String["indigo","yellow","orange","blue","violet","green","red"])

两个集合的并集是包含一个或另一个集合中所有元素的集合。结果是另一个集合 - 因此,即使我们每个集合中都有一个“黄色”,您也不能在这里有两个“黄色”。

julia> union(colors, rainbow)
Set(String["indigo","yellow","orange","blue","violet","green","black","red"])

两个集合的交集是包含属于两个集合中所有元素的集合

julia> intersect(colors, rainbow)
Set(String["yellow","blue","green","red"])

两个集合之间的差集是包含第一个集合中所有元素的集合,但不在第二个集合中。这次,您提供集合的顺序很重要。setdiff()函数查找第一个集合colors中但不在第二个集合rainbow中的元素

julia> setdiff(colors, rainbow)
Set(String["black"])

其他函数

[编辑 | 编辑源代码]

作用于数组和集合的函数有时也作用于字典和其他集合。例如,一些集合操作可以应用于字典,而不仅仅是集合和数组

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"

请注意,结果以 Pair 数组的形式返回,而不是字典的形式。

我们已经看到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)

字符串和字符

[编辑 | 编辑源代码]
Previous page
字典和集合
Julia 入门 Next page
使用文本文件
字符串和字符

字符串和字符

[编辑 | 编辑源代码]

字符串

[edit | edit source]

字符串是由一个或多个字符组成的序列,通常用双引号括起来

"this is a string"

关于字符串,你需要知道两件重要的事情。

首先,它们是不可变的。一旦创建,你就无法改变它们。但从现有字符串的一部分创建新的字符串很容易。

其次,在使用两个特定字符时要小心:双引号 (") 和美元符号 ($)。如果你想在字符串中包含双引号字符,它必须以反斜杠开头,否则字符串的其余部分将被解释为 Julia 代码,可能会产生有趣的结果。如果你想在字符串中包含美元符号 ($),它也应该以反斜杠开头,因为它用于字符串插值.

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" " 表示不进行插值的原始字符串

字符串插值

[edit | edit source]

你经常希望在字符串中使用 Julia 表达式的结果。例如,假设你想说

"The value of x is n."

其中 nx 的当前值。任何 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."

子字符串

[edit | edit source]

要从字符串中提取较小的字符串,请使用 getindex(s, range)s[range] 语法。对于基本 ASCII 字符串,你可以使用与从数组中提取元素相同的技术

julia> s ="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"

它等效于

julia> s[begin+2: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 字符串

[edit | edit source]

并非所有字符串都是 ASCII。要访问 Unicode 字符串中的单个字符,你不能总是使用简单的索引,因为某些字符占据多个索引位置。不要仅仅因为某些索引号似乎有效而被欺骗

julia> su = "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

不要使用 length(str) 来查找字符串的长度,而要使用 lastindex(str)

julia> length(su)
6
julia> lastindex(su)
10

isascii() 函数测试字符串是否为 ASCII 或包含 Unicode 字符

julia> isascii(su)
false

在这个字符串中,'第二个'字符 é 有 2 个字节,'第四个'字符 𐅍 有 4 个字节。

for i in eachindex(su)
    println(i, " -> ", su[i])
end
1 -> A
2 -> é
4 -> B
5 -> 𐅍
9 -> C
10 -> D

‘第三’个字符 B 从字符串中的第 4 个元素开始。

你也可以使用 pairs() 函数更轻松地做到这一点

for pair in pairs(su)
    println(pair)
end
1 => A
2 => é
4 => B
5 => 𐅍
9 => C
10 => D

或者,使用 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'



还有其他用于处理此类字符串的有用函数,包括 collect()thisind()nextind()prevind()

julia> collect(su)
 6-element Array{Char,1}:
 'A'
 'é'
 'B'
 '𐅍'
 'C'
 'D'
for i in 1:10
    print(thisind(su, i), " ")
end
1 2 2 4 5 5 5 5 9 10 

拆分和连接字符串

[edit | edit source]

你可以使用乘法 (*) 运算符将字符串粘在一起(这个过程通常称为连接

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."

使用函数分割

[edit | edit source]

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"

字符对象

[edit | edit source]

上面我们从较大的字符串中提取了较小的字符串

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')
('𐅍','♀','&')

对于字符串,\Uxxxxxxxx\uxxxx 语法更加严格。

julia> "\U0001014d2\U000026402\u26402\U000000a52\u00a52\U000000352\u00352\x352"
"𐅍2♀2♀2¥2¥2525252"

在数字和字符串之间转换

[edit | edit source]

将整数转换为字符串是 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

在字符和整数之间转换

[edit | edit source]

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 代码编号),请尝试以下操作

julia> Int("S"[1])
83

对于快速字母表

julia> string.(Char.("A"[1]:"Z"[1])) |> collect 
26-element Array{String,1}:
 "A"
 "B"
 ...
 "Y"
 "Z"

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() 之类的函数来“读取”此字符串,该函数是“带分隔符读取”函数。该函数位于 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() 将路径名字符串分解为一个字符对象数组,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"

您使用 => 运算符指定要查找的模式及其替换内容。通常第三个参数是另一个字符串,如这里所示。但您也可以提供一个函数来处理结果

julia> replace("Sherlock Holmes", "e" => uppercase)
"ShErlock HolmEs"

其中函数(这里为内置的 uppercase() 函数)应用于匹配的子字符串。

没有 replace! 函数,其中“!”表示修改其参数的函数。这是因为您无法修改字符串——它们是不可变的。

使用函数替换
[编辑 | 编辑源代码]

Julia 中的许多函数允许您在函数调用中提供函数,并且您可以很好地利用匿名函数来实现这一点。例如,以下是如何使用函数在 replace() 函数中提供随机替换。

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."

正则表达式

[编辑 | 编辑源代码]

您可以使用正则表达式来查找子字符串的匹配项。一些接受正则表达式的函数有

  • replace() 更改正则表达式的出现次数
  • match() 返回第一个匹配项或无匹配项
  • eachmatch() 返回一个迭代器,允许您搜索所有匹配项
  • split() 在每个匹配项处分割字符串

使用 replace() 将每个辅音替换为下划线

julia> replace("Elementary, my dear Watson!", r"[^aeiou]" => "_")
"__e_e__a________ea___a__o__"

以下代码将每个元音替换为对每个匹配项运行函数的结果

julia> replace("Elementary, my dear Watson!", r"[aeiou]" => uppercase)
"ElEmEntAry, my dEAr WAtsOn!"

使用 replace(),您可以访问匹配项,前提是您提供了一个特殊的替换字符串 s"",其中 \1 指的是第一个匹配项,\2 指的是第二个匹配项,依此类推。使用此正则表达式操作,每个前导空格的小写字母都会重复三次

julia> replace("Elementary, my dear Watson!", r"(\s)([a-z])" => s"\1\2\2\2")
"Elementary, mmmy dddear Watson!"

对于更多正则表达式的乐趣,可以使用 -match- 函数。

在这里,我已将“福尔摩斯探案集”的完整文本从文件中加载到名为 text 的字符串中

julia> f = "/tmp/adventures-of-sherlock-holmes.txt"
julia> text = read(f, String);

要使用匹配可能性作为布尔条件(例如,适合用在 if 语句中),请使用 occursin()

julia> occursin(r"Opium", text)
false

这很奇怪。我们希望找到这位伟大的侦探奇特药理娱乐的证据。事实上,单词“鸦片”确实出现在文本中,但只出现在小写形式,因此产生了这个 false 结果——正则表达式区分大小写。

julia> occursin(r"(?i)Opium", text)
true

这是一个不区分大小写的搜索,由标志 (?i) 设置,它返回 true

您可以使用简单的循环检查每一行是否包含该词

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,

为了获得更易用的输出(在 REPL 中),请添加 enumerate() 和一些突出显示

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,

添加正则表达式修饰符(例如不区分大小写的匹配)还有另一种语法。请注意,在第二个示例中,正则表达式字符串后面紧跟一个“i”

julia> occursin(r"Opium", text)
false

julia> occursin(r"Opium"i, text)
true

使用 eachmatch() 函数,您可以将正则表达式应用于字符串以生成一个迭代器。例如,要查找文本中匹配字母“L”,后面跟着其他一些字符,以“ed”结尾的子字符串

julia> lmatch = eachmatch(r"L.*?ed", text)

lmatch 中的结果是一个可迭代对象,包含所有匹配项,作为 RegexMatch 对象

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

我们可以遍历迭代器并依次查看每个匹配项。您可以访问 RegexMatch 的多个字段,以提取有关匹配项的信息。这些字段包括 capturesmatchoffsetoffsetsregex。例如,match 字段包含匹配的子字符串

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

其他字段包括 captures(作为字符串数组的捕获的子字符串)、offset(整个匹配项开始的字符串中的偏移量)和 offsets(捕获的子字符串的偏移量)。

要获取匹配字符串数组,请使用类似以下代码

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" 

基本的 match() 函数查找正则表达式的第一个匹配项。使用 match 字段从 RegexMatch 对象中提取信息

julia> match(r"She.*",text).match
"Sherlock Holmes she is always THE woman. I have seldom heard\r"

从文件中获取匹配行的更简化的方式是

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,"

创建正则表达式

[编辑 | 编辑源代码]

有时您想从代码中创建正则表达式。您可以通过创建 Regex 对象来实现。以下是如何计算文本中元音数量的一种方法

f = open("sherlock-holmes.txt")

text = read(f, String)

for vowel in "aeiou"
    r = Regex(string(vowel))
    l = [m.match for m = eachmatch(r, text)]
    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.
创建替换字符串
[编辑 | 编辑源代码]

有时您需要组装替换字符串。为此,您可以使用 SubstitutionString() 而不是 s"..."

例如,假设您想在替换字符串中进行一些字符串插值。也许您有一个文件列表,您想对它们重新编号,以便“file2.png”变为“file1.png”

files = ["file2.png", "file3.png", "file4.png", "file5.png", "file6.png", "file7.png"] 

for (n, f) in enumerate(files)
    newfilename = replace(f, r"(.*)\d\.png" => SubstitutionString("\\g<1>$(n).png"))
    # now to do the renaming...

请注意,您不能简单地在 SubstitutionString 中使用 \1 来引用第一个捕获的表达式,您必须将其转义为 \\1,并使用 \g(转义为 \\g)来引用命名的捕获组。

测试和更改字符串

[编辑 | 编辑源代码]

有许多函数用于测试和更改字符串

  • length(str) 字符串长度
  • sizeof(str) 长度/大小
  • startswith(strA, strB) strA 是否以 strB 开头?
  • endswith(strA, strB) strA 是否以 strB 结尾?
  • occursin(strA, strB) strA 是否出现在 strB 中?
  • all(isletter, str) str 是否全部是字母?
  • all(isnumeric, str) str 是否全部是数字字符?
  • isascii(str) str 是否是 ASCII?
  • all(iscntrl, str) str 是否全部是控制字符?
  • all(isdigit, str) str 是否是 0-9?
  • all(ispunct, str) str 是否由标点符号组成?
  • all(isspace, str) str 是否是空白字符?
  • all(isuppercase, str) str 是否全部是大写字母?
  • all(islowercase, str) str 是否全部是小写字母?
  • all(isxdigit, str) str 是否全部是十六进制数字?
  • uppercase(str) 返回 str 的副本,转换为大写
  • lowercase(str) 返回 str 的副本,转换为小写
  • titlecase(str) 返回 str 的副本,每个词的首字母都转换为大写
  • uppercasefirst(str) 返回 str 的副本,首字母转换为大写
  • lowercasefirst(str) 返回将第一个字符转换为小写字母的 str 的副本。
  • chop(str) 返回删除最后一个字符的副本。
  • chomp(str) 返回删除最后一个字符的副本,但前提是它是换行符。

要写入字符串,可以使用 Julia 流。sprint()(字符串打印)函数允许使用函数作为第一个参数,并使用该函数和其余参数将信息发送到流,并将结果作为字符串返回。

例如,考虑以下函数 f。函数的主体将匿名“打印”函数映射到参数,并在它们周围用尖括号括起来。当 sprint 使用该函数时,函数 f 会处理其余参数并将它们发送到流。

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

println() 这样的函数可以接受 IOBuffer 或流作为它们的第一个参数。这使您可以将打印内容输出到流而不是标准输出设备。

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

此后,名为 iobuffer 的内存流将充满数字和换行符,即使终端上没有打印任何内容。要将 iobuffer 的内容从流复制到字符串或数组,可以使用 take!()

julia> String(take!(iobuffer))
"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14 ... \n98\n99\n100\n"

彩色 / 样式化输出

[编辑 | 编辑源代码]

以下使用 printstyled 以各自的颜色打印出消息。

julia> for color in [:red, :green, :blue, :magenta]
           printstyled("Hello in $(color)\n"; color = color)
       end
Hello in red
Hello in green
Hello in blue
Hello in magenta

打印格式化的回溯

[编辑 | 编辑源代码]

在 try catch 语句的中间,以下代码将打印导致异常的原始回溯。

try
    # some code that can fail, but you want to continue even after a failure
catch e
    # show the error, but with its backtrace
    showerror(stderr, e, catch_backtrace())
end

如果您不在 try-catch 之外,并且希望打印堆栈跟踪而不停止执行,请使用此方法。

showerror(stderr, ErrorException("show stacktrace"), stacktrace())

使用文本文件

[编辑 | 编辑源代码]
Previous page
字符串和字符
Julia 入门 Next page
使用日期和时间
使用文本文件

从文件读取

[编辑 | 编辑源代码]

从文本文件获取信息的标准方法是使用 open()read()close() 函数。

要从文件读取文本,首先获取文件句柄。

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 块的更多信息,请参见 控制流程

由于块中局部变量的范围,您可能想要保留一些已处理的信息。

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() 函数很有用——它会从字符串中删除尾随换行符。

eachline() 函数将源代码转换为迭代器。这使您可以一次处理一行文件。

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() 函数,然后再次关闭它,并将处理后的内容分配给变量。

您可以使用 CSV.jl 读取和写入逗号分隔值 (.csv) 文件,建议您使用它(比使用 DelimitedFiles.readdlm() 函数处理更多边缘情况,并且可以更快,尤其是对于大型文件),以便读取以特定字符分隔的行,例如数据文件、存储为文本文件的数组和表格。如果您使用 DataFrames 包,还有一个专门用于将数据读入表格的 readtable()

使用路径和文件名

[编辑 | 编辑源代码]

这些函数对于使用文件名非常有用。

  • cd(path) 更改当前目录。
  • pwd() 获取当前工作目录。
  • readdir(path) 返回命名目录或当前目录的内容列表。
  • abspath(path) 将当前目录的路径添加到文件名以创建绝对路径名。
  • joinpath(str, str, ...) 从多个部分组装路径名。
  • isdir(path) 告诉您路径是否为目录。
  • splitdir(path) – 将路径拆分为目录名称和文件名元组。
  • splitdrive(path) – 在 Windows 上,将路径拆分为驱动器号部分和路径部分。在 Unix 系统上,第一个组件始终为空字符串。
  • splitext(path) – 如果路径的最后一个组件包含点,则将路径拆分为点之前的部分和包括点之后的部分。否则,返回元组,其中包含未修改的参数和空字符串。
  • expanduser(path) – 将路径开头的波浪号替换为当前用户的家目录。
  • normpath(path) – 规范化路径,删除“.”和“..”条目。
  • realpath(path) – 通过扩展符号链接并删除“.”和“..”条目来规范化路径。
  • homedir() – 获取当前用户的家目录。
  • dirname(path) – 获取路径的目录部分。
  • basename(path) – 获取路径的文件名部分。

要处理目录中选定的文件,可以使用 filter() 和匿名函数来过滤文件名,只保留想要的文件。(filter() 更像是一个渔网或筛子,而不是咖啡过滤器,因为它会捕捉想要保留的内容。)

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

如果您想使用正则表达式匹配一组文件,请使用 occursin()。让我们查找具有“.jpg”或“.png”后缀的文件(记住要转义“.”)。

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

要检查文件层次结构,可以使用 walkdir(),它使您能够遍历目录,并依次检查每个目录中的文件。

文件信息

[编辑 | 编辑源代码]

如果您需要有关特定文件的信息,请使用 stat("pathname"),然后使用其中一个字段来查找信息。以下是如何获取所有信息以及列出文件“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

您可以通过“stat”结构访问这些字段

julia> s = stat("Untitled1.ipynb")
StatStruct(mode=100644, size=64424)
julia> s.ctime
1.446649269e9

您也可以直接使用其中一些字段

julia> ctime("Untitled2.ipynb")
1.446649269e9

虽然不包括 size

julia> s.size
64424

要处理符合条件的特定文件——例如,所有 Jupyter 文件(即扩展名为“ipynb”的文件)在特定日期之后修改——您可以使用类似以下代码。

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

与文件系统交互

[编辑 | 编辑源代码]

cp()mv()rm()touch() 函数与其 Unix shell 对应函数具有相同的名称和功能。

要将文件名转换为路径名,请使用 abspath()。您可以将此方法应用于目录中的文件列表。

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"
...

要将列表限制为包含特定子字符串的文件名,请在 filter() 中使用匿名函数 - 例如:

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"
...

要将列表限制为正则表达式匹配项,请尝试以下操作:

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"

写入文件

[编辑 | 编辑源代码]

要写入文本文件,请使用 "w" 标志打开它,并确保您有权在指定目录中创建文件。

open("/tmp/t.txt", "w") do f
    write(f, "A, B, C, D\n")
end

以下是如何写入 20 行,每行包含 4 个介于 1 到 10 之间的随机数,用逗号分隔。

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

另一种更快的选择是使用 DelimitedFiles.writedlm() 函数,将在下面介绍。

using DelimitedFiles
writedlm("/tmp/test.txt", rand(1:10, 20, 4), ", ")

将数组写入和读取文件

[编辑 | 编辑源代码]

在 DelimitedFiles 包中,有两个方便的函数,writedlm()readdlm()。这些函数允许您将数组或集合从文件读取/写入文件。

writedlm() 将对象的內容写入文本文件,readdlm() 将数据从文件读取到数组。

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)

您可以使用 shell 查看文件(键入分号 ";" 切换)。

<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

元素用制表符分隔,除非您指定其他分隔符。这里,用冒号分隔数字。

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

要从文本文件读取数据,可以使用 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

还有一些专门用于读取和写入文件数据的 Julia 包,包括 DataFrames.jl 和 CSV.jl。在 JuliaHubJuliaPackages 中搜索这些包和其他包。许多这些包都位于 JuliaData 组织的主页。

使用日期和时间

[编辑 | 编辑源代码]
Previous page
使用文本文件
Julia 入门 Next page
绘图
使用日期和时间

使用日期和时间

[编辑 | 编辑源代码]

标准包 Dates 中提供了用于处理日期和时间的函数。要使用任何时间和日期函数,您必须执行以下操作之一:

  • using Dates
  • import Dates

如果您使用 import Dates 函数,您需要在每个函数前面加上显式的 Dates.,例如 Dates.dayofweek(dt),如本章所示。但是,如果您在代码中添加 using Dates 行,则会将所有导出的 Dates 函数引入 Main,并且可以使用这些函数而不带 Dates. 前缀。

此图显示了用于存储时间、日期和日期时间的各种类型之间的关系。

Shows the hierarchy of date and date-time types in Julia
显示了 Julia 中日期和日期时间类型的层次结构。

日期、时间和日期时间

[编辑 | 编辑源代码]

有三个主要的数据类型可用。

  • Dates.Time 对象表示一天中一个精确的时间点。它没有说明星期几或年份。它精确到纳秒。
  • Dates.Date 对象仅表示日期:没有时区,没有夏令时问题等等... 它精确到,好吧,一天。
  • Dates.DateTime 对象是日期和时间点的组合,因此它指定了一个确切的时间点。它精确到毫秒左右。

使用以下构造函数之一创建您想要的类型对象。

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

Dates.today() 函数返回当前日期的 Date 对象。

julia> datetoday = Dates.today()
2014-09-02

Dates.now() 函数返回当前时间点的 DateTime 对象。

julia> datetimenow = Dates.now()
2014-09-02T08:20:07.437

(我们之前使用 Dates.now() 来定义 rightnow,然后使用 Dates.Time() 将其转换为 Dates.Time。)

有时您需要 UTC(世界参考时间,没有夏令时等本地调整)。

julia> Dates.now(Dates.UTC)
2014-09-02T08:27:54.14

要从格式化字符串创建对象,请在 Dates 中使用 DateTime() 函数,并提供一个与格式匹配的适当格式字符串。

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

有关更多示例,请参阅下面的 日期格式化

日期和时间查询

[编辑 | 编辑源代码]

获得日期/时间或日期对象后,您可以使用以下函数从中提取信息。对于日期和日期时间对象,您可以获得年份、月份、日期等等。

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

以及,对于日期/时间对象:

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

还有一些其他有用的函数:

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 

其中两个函数的名称非常相似:Dates.daysofweekinmonth()(星期几月份内)函数告诉您一个月中与指定日期同一天名称的日期有多少天 - 当前月份(在撰写本文时)有五个星期二。最后一个函数 dayofweekofmonth(birthday)(星期几月份内),告诉我们 1997 年 3 月 15 日是星期六,是那个月的第三个星期六。

您还可以找到相对于日期的日期,例如包含该日期的星期的第一天,使用调整函数,将在下面介绍。

日期运算

[编辑 | 编辑源代码]

您可以对日期和日期/时间对象进行运算。减去两个日期或日期时间以找到差值是最明显的运算之一。

julia> datetoday - birthday
6380 days

julia> datetimenow - armistice
3023472252000 milliseconds

您可以将其转换为 Dates.DayDates.Millisecond 或其他单位。

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

要将时间段加到或减去日期和日期/时间对象,请使用 Dates. 构造函数来指定时间段。例如,Dates.Year(20) 定义一个 20 年的时间段,Dates.Month(6) 定义一个 6 个月的时间段。因此,要将 20 年和 6 个月添加到生日日期:

julia> birthday + Dates.Year(20) + Dates.Month(6)
2017-09-15

以下是现在 6 个月前:

julia> Dates.now() - Dates.Month(6)
2014-03-02T16:43:08

同样适用于月、周:

julia> Dates.now() - Dates.Year(2) - Dates.Month(6)
2012-03-02T16:44:03

同样适用于周和小时。以下是现在两周 12 小时的日期和时间:

julia> Dates.now() + Dates.Week(2) + Dates.Hour(12)
2015-09-18T20:49:16

并且有:

julia> daystoxmas = Dates.Date(Dates.year(Dates.now()), 12, 25) - Dates.today()
148 days

或到圣诞节还有 148 天(在撰写本文时)。

要检索数字值,请使用函数 Dates.value()

julia> Dates.value(daystoxmas)
148

这同样适用于不同的日期/时间对象类型。

julia> lastchristmas = Dates.now() - Dates.DateTime(2017, 12, 25, 0, 0, 0)
25464746504 milliseconds

julia> Dates.value(lastchristmas)
25464746504

日期范围

[编辑 | 编辑源代码]

您可以创建可迭代的范围对象,这些对象定义了日期范围。

julia>  d = Dates.Date(1980,1,1):Dates.Month(3):Dates.Date(2019,1,1)
1980-01-01:3 months:2019-01-01

此迭代器会产生每三个月的第一天。要找出其中哪些是工作日,您可以提供一个匿名函数到 filter(),该函数会将工作日名称与给定工作日名称进行比较。

julia> weekdays = filter(dy -> Dates.dayname(dy) != "Saturday" && Dates.dayname(dy) != "Sunday" , d)

104 元素的 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

同样地,以下是现在起,一年后,每隔 3 小时的时间范围。

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

如果您必须每 30 天支付一次账单,从 2018 年 1 月 1 日开始,以下代码展示了到期日期如何在每个月向前推移。

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

日期格式化

[编辑 | 编辑源代码]

要指定日期格式,请在格式字符串中使用日期格式代码。每个字符都对应一个日期/时间元素。

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

您可以将这些格式字符串与 DateTime()Dates.format() 等函数一起使用。例如,您可以通过识别传入字符串中的不同元素,从字符串创建 DateTime 对象。

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

其他字符将按字面意义使用。在第二个示例中,格式化字符匹配如下:

Fri, 15 Jun 2018 11:43:14
e  ,  d   u    y  H: M: S

您可以将格式字符串提供给 Dates.format 以格式化日期对象。在格式字符串中,您可以重复字符以控制输出年份和日期的方式。

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"

在创建格式化日期时,您可以将格式字符串中的某些组件加倍,为一位数日期元素产生前导零。

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"

要将日期字符串从一种格式转换为另一种格式,您可以使用 DateTime() 和格式字符串将字符串转换为 DateTime 对象,然后使用 DateFormat() 以不同格式输出对象。

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"

如果您要进行大量日期格式化(您可以将日期函数应用于字符串数组),最好预先定义一个 DateFormat 对象,然后将其用于批量转换(这样更快)。

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

您可以使用一些内置格式。例如,您可以使用 Dates.ISODateTimeFormat 来获得 ISO8601 格式。

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

这里还有古老的 RFC1123。

julia> Dates.format(Dates.now(), Dates.RFC1123Format)
"Sat, 30 Jul 2016 16:36:09"

日期调整

[edit | edit source]

有时您需要找到最接近另一个日期的日期 - 例如,该周的第一天,或包含该日期的月的最后一天。您可以使用Dates.firstdayofweek()Dates.lastdayofmonth()之类的函数来执行此操作。因此,如果我们目前处于本周中旬

julia> Dates.dayname(now())
"Wednesday"

本周的第一天由以下代码返回

julia> Dates.firstdayofweek(now())
2014-09-01T00:00:00

您也可以使用函数链运算符编写此代码

julia> Dates.now() |> Dates.firstdayofweek |> Dates.dayname 
"Monday"

tofirst()tolast()tonext()toprev()方法提供了更通用的解决方案。

使用tonext()toprev(),您可以提供一个(可能是匿名的)函数,该函数在日期正确调整后返回 true。例如,函数

d->Dates.dayofweek(d) == Dates.Tuesday

如果日期d是星期二,则返回 true。将此与tonext()方法一起使用

julia> Dates.tonext(d->Dates.dayofweek(d) == Dates.Tuesday, birthday)
1997-03-18 # the first Tuesday after the birthday

或者您可以找到生日日期后的下一个星期日

julia> Dates.tonext(d->Dates.dayname(d) == "Sunday", birthday)
1997-03-16 # the first Sunday after the birthday

使用tofirst()tolast(),您可以找到一个月的第一个星期日,或者星期四,或者任何其他星期。星期一是 1,星期二是 2,依此类推。

julia> Dates.tofirst(birthday, 1) # the first Monday (1) of that month
1997-03-03

提供关键字参数of=Year以获取当年第一个匹配的星期几。

julia> Dates.tofirst(birthday, 1, of=Year) # the first Monday (1) of 1997
1997-01-06

四舍五入日期和时间

[edit | edit source]

您可以使用round()floor()ceil()(通常用于将数字四舍五入到最近的首选值)来调整日期,使其在时间上向前或向后移动,从而使其具有更“圆整”的值。

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"

ceil()将日期或时间向前调整

julia> ceil(birthday, Dates.Month)
1997-04-01

julia> ceil(birthday, Dates.Year)
1998-01-01

julia> ceil(birthday, Dates.Week)
1997-03-17

重复日期

[edit | edit source]

能够在日期范围内找到满足特定条件的所有日期非常有用。例如,您可以使用Dates.dayofweekofmonth()Dates.dayname()函数来计算一个月的第二个星期日。

例如,让我们创建一个从 2014 年 9 月 1 日到 2014 年圣诞节的日期范围

julia> dr = Dates.Date(2014,9,1):Dates.Day(1):Dates.Date(2014,12,25)
2014-09-01:1 day:2014-12-25

现在,一个类似于我们之前在tonext()中使用的匿名函数会找到该范围内满足该函数的选定日期

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

这些是 2014 年 9 月 1 日到 2014 年圣诞节之间的每个星期日的日期。

通过在匿名函数中组合条件,您可以构建更复杂的重复事件。以下是在该期间内所有位于奇数天且大于 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

以下是在 2016 年 4 月至 11 月之间的每个第二个星期二

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 时间

[edit | edit source]

有时您必须处理另一种时间记录方式:Unix 时间。Unix 时间是从 1970 年(Unix 的诞生)开始以来的秒数。在 Julia 中,计数存储在 64 位整数中,我们永远不会看到 Unix 时间的结束。(宇宙将在 64 位 Unix 时间达到最大可能值之前很久结束,该值将在距今约 2920 亿年后,即 292,277,026,596 年 12 月 4 日星期日 15:30:08。)

在 Julia 中,time()函数(不带参数使用)返回当前秒的 Unix 时间值

julia> time()
1.414141581230945e9

strftime()(“字符串格式时间”)函数(位于 Libc 模块中)将 Unix 时间中的秒数转换为更易读的格式

julia> Libc.strftime(86400 * 365.25 * 4) # 4 years worth of Unix seconds
"Tue  1 Jan 00:00:00 1974"

您可以通过提供格式字符串来选择不同的格式,其中日期和时间的不同组件由“%”字母代码定义

julia> Libc.strftime("%A, %B %e at %T, %Y", 86400 * 365.25 * 4)
"Tuesday, January  1 at 00:00:00, 1974"

strptime()函数接受格式字符串和日期字符串,并返回 TmStruct 表达式。然后可以通过将其传递给time()将其转换为 Unix 时间值

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

Dates 模块还提供unix2datetime()函数,该函数将 Unix 时间值转换为日期/时间对象

julia> Dates.unix2datetime(time())
2014-10-24T09:26:29.305

时间的瞬间

[edit | edit source]

DateTime存储为毫秒,在字段instant中。使用Dates.value获取值。

julia> moment=Dates.now()
2017-02-01T12:45:46.326
  
julia> Dates.value(moment)
63621636346326
julia> moment.instant
Base.Dates.UTInstant{Base.Dates.Millisecond}(63621636346326 milliseconds)

如果您使用更精确的Dates.Time类型,则可以访问纳秒。

julia> moment = Dates.Time(Dates.now())
17:38:44.33
julia> Dates.value(moment)
63524330000000
 
julia> moment.instant
63524330000000 nanoseconds

计时和监控

[edit | edit source]

@elapsed宏返回表达式评估所需的时间(以秒为单位)

function test(n)
     for i in 1:n
        x = sin(rand())
     end
end
julia> @elapsed test(100000000)
1.309819509

@time宏会告诉您表达式评估所需的时间以及内存分配情况。

julia> @time test(100000000)
2.532941 seconds (4 allocations: 160 bytes)

绘图

[edit | edit source]
Previous page
使用日期和时间
Julia 入门 Next page
元编程
绘图

绘图

[edit | edit source]

Julia 中有许多不同的绘图包,其中可能有一个适合您的需求和喜好。本节简要介绍了其中一个,Plots.jl,它很有趣,因为它与许多其他绘图包进行了对话。在使用 Julia 进行绘图之前,请下载并安装第一个绘图包或任何您选择的绘图包(要获取提示,请按]

(v1.0) pkg> add Plots PyPlot GR UnicodePlots  # See also Gnuplot.jl (and Gaston.jl alternative for)

第一个包 Plots 是一个高级绘图包,它与其他绘图包(这里称为“后端”)进行交互。它们充当生成图形的图形“引擎”。这些中的每一个也是一个独立的绘图包,可以单独使用,但是使用 Plots 作为接口的优势在于,正如您将看到的,它具有更简单、更一致的接口。

另请参阅功能强大的Makie.jl,它与 Plots.jl 无关,并且有自己的后端,例如 GLMakie.jl,以及许多扩展,例如 AlgebraOfGraphics.jl(这些扩展在本文中没有更多解释,除了这个安装示例)

(v1.6) pkg> add GLMakie AlgebraOfGraphics

您可以在 Julia 会话中以通常的方式开始使用 Plots.jl 包

julia> using Plots

您通常希望绘制一个或多个**系列**,即数值数组。或者,您可以提供一个或多个函数来生成数值。

在此示例中,我们将绘制 2022 年 5 月份的月相(照亮部分)。

julia> using AstroLib  # add if necessary with ] add AstroLib
julia> using Dates 
julia> points = DateTime(2022,05,01):Dates.Hour(1):DateTime(2022,05,31,23,59,59)
julia> moonphases = mphase.(jdcnv.(points)) 

现在我们有了一个 Float64 值数组,每个小时对应一个值,表示月球圆盘的照亮程度

julia> moonphases
744-element Vector{Float64}:
 0.0002806471321559201
 0.00041259024384365794
 0.0005791256946680035
 0.0007801698949687075
 0.0010156372084771381
 0.0012854399754271273
 ⋮
 0.015263669925646928
 0.016249662554591593
 0.017266056993952783
 0.018312770267986556
 0.019389718259650524
 0.020496815690093984

要绘制这些系列,只需将它们传递给 Plots 的plot()函数。

julia> plot(points, moonphases)

这使用了第一个可用的绘图引擎(GR.jl)。Plots 添加了其他绘图“家具”,然后为您绘制了所有内容。

如果您想切换到不同的引擎,请使用提供的函数之一:gr()unicodeplots()plotly()等等。例如,要切换到使用 Unicodeplots 绘图包(它使用 Unicode 字符来制作绘图,非常适合在 REPL/终端中使用),请执行以下操作

 julia> '''unicodeplots()'''
 
 julia> '''plot(moonphases)'''
    
     ┌────────────────────────────────────────┐ 
   1 │                .:''':.                 │ 
     │               .:     '.                │ 
     │              :'       ':               │ 
     │             :'         '.              │ 
     │            .'           :.             │ 
     │           .:             :             │ 
     │          .:               :            │ 
     │         .:                ':           │ 
     │         :                  :.          │ 
     │        :                    :.         │ 
     │       :'                     :.        │ 
     │      :'                       :.       │ 
     │    .:                          :.      │ 
     │   .:                            :.     │ 
   0 │..:'                              ':....│ 
     └────────────────────────────────────────┘

自定义绘图

[edit | edit source]

Plots.jl 包有大量文档,研究完它后,您将能够花费数小时调整和自定义绘图以满足您的需求。以下只是一个一年中每一天的时间方程的绘图示例。

x 轴上的刻度是 1:365 中的数字。最好能看到日期本身。首先,创建字符串

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

xticks选项提供的值为一个元组,它包含两个数组/范围

(xticks = (1:14:366, datestrings[1:14:366])

第一个提供数值,第二个提供刻度的匹配文本标签。

额外的标签和图例很容易添加。您可以从 Colors.jl 包中访问颜色

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                     
    )                                             

examples of plotting in Julia using Plots.jl

其他包

[edit | edit source]

UnicodePlots

[edit | edit source]

如果你经常使用 REPL,你可能想要一种快速简便的方法来绘制使用文本而不是图形作为输出的绘图?UnicodePlots.jl 包使用 Unicode 字符来绘制各种绘图,避免了加载各种图形库的需要。它可以生成

  • 散点图
  • 线图
  • 条形图(水平)
  • 阶梯图
  • 直方图(水平)
  • 稀疏模式
  • 密度图

下载并将其添加到你的 Julia 安装中,如果你还没有这样做

pkg> add UnicodePlots

你只需要做一次。现在你加载模块并导入函数

julia> using UnicodePlots

以下是一个线图的简单示例

julia> myPlot = lineplot([1, 2, 3, 7], [1, 2, -5, 7], title="My Plot", border=:dotted)
                       My Plot
      ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
   10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠔⠒⠊⠉⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠉⠉⠉⠉⠉⠉⠉⠉⠉⠫⡉⠉⠉⠉⠉⠉⢉⠝⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⢀⡠⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
  -10 ⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
      ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
      0                                       10

这是一个密度图

julia> myPlot = densityplot(collect(1:100), randn(100), border=:dotted)
      ⡤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤
   10 ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                            ░           ⢸
      ⡇ ░░░        ░ ▒░  ▒░     ░  ░ ░ ░ ░   ░ ⢸
      ⡇░░  ░▒░░▓▒▒ ▒░░ ▓░░ ░░░▒░ ░ ░   ▒ ░ ░▒░░⢸
      ⡇▓▒█▓▓▒█▓▒▒▒█▒▓▒▓▒▓▒▓▓▒▓▒▓▓▓█▒▒█▓▒▓▓▓▓▒▒▒⢸
      ⡇    ░     ░         ░░░ ░    ▒ ░ ░ ░░ ░ ⢸
      ⡇                          ░             ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
      ⡇                                        ⢸
  -10 ⡇                                        ⢸
      ⠓⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠒⠚
      0                                      100

(请注意,它需要终端环境才能使显示的图形 100% 成功 - 当你复制粘贴时,一些魔法会消失。)

VegaLite

[edit | edit source]

允许你创建网页浏览器窗口中的可视化。VegaLite 是一种可视化语法,一种用于创建和保存可视化设计的声明性格式。使用 VegaLite,你可以用 JSON 格式描述数据可视化,并使用 HTML5 Canvas 或 SVG 生成交互式视图。你可以生成

  • 面积图
  • 条形图/直方图
  • 线图
  • 散点图
  • 饼图/圆环图
  • 瀑布图
  • 词云

要使用 VegaLite,首先将包添加到你的 Julia 安装中。你只需要做一次

pkg> add VegaLite

以下是创建堆叠面积图的方法。

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)

graphic created with Julia and Vega.jl

VegaLite 的一个通用功能是,你可以在创建可视化后修改它。因此,让我们使用一个函数来更改配色方案(注意“!”表示参数被修改)

julia> colorscheme!(a, ("Reds", 3))

graphic created with Julia and Vega.jl

你可以通过提供两个数组轻松地创建饼图(和圆环图)。x 数组提供标签,y 数组提供数量

julia> fruit = ["peaches", "plums", "blueberries", "strawberries", "bananas"];
julia> bushels = [100, 32, 180, 46, 21];
julia> piechart(x = fruit, y = bushels, holesize = 125)

a pie/donut chart created in Julia/Vega.jl

元编程

[edit | edit source]
Previous page
绘图
Julia 入门 Next page
模块和包
元编程

什么是元编程?

[edit | edit source]

元编程是指你编写 Julia 代码来处理和修改 Julia 代码。使用元编程工具,你可以编写 Julia 代码来修改源文件中的其他部分,甚至控制修改后的代码何时以及是否运行。

在 Julia 中,原始源代码的执行分为两个阶段。(实际上阶段不止两个,但在这里我们将重点关注这两个。)

阶段 1 是当你解析原始 Julia 代码时 - 将其转换为适合评估的格式。你应该熟悉这个阶段,因为这是所有语法错误被发现的时候...... 这个阶段的结果是抽象语法树或 AST (抽象语法树),它是一个包含所有代码的结构,但它以一种比人类友好语法更容易操作的格式表示。

阶段 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 评估。相反,它报告将使用哪些方法来处理这些特定的参数。它还会告诉你包含该方法定义的源文件以及行号。

元编程的其他用途包括通过编写生成较长代码的短代码片段来自动执行繁琐的编码工作,以及通过生成可能你不想手动编写的更快的代码来提高“标准”代码的性能。

引用的表达式

[edit | edit source]

为了使元编程成为可能,Julia 必须有一种方法来存储未评估但已解析的表达式,一旦解析阶段完成。这就是“:”(冒号)前缀运算符

julia> x = 3
3

julia> :x
:x 

对于 Julia,:x 是一个未评估或引用的符号。

(如果你不熟悉计算机编程中引用符号的使用,想想在写作中如何使用引号来区分普通用法和特殊用法。例如,在句子

'Copper' 包含六个字母。

中的引号表示单词 '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

它已解析、准备就绪,可以随时使用。

评估表达式

[edit | edit source]

还有一个用于评估未评估表达式的函数。它称为 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

更有用的是,可以在表达式评估之前修改它的内容。

表达式内部

[edit | edit source]

一旦你将 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 字段为 :blockargs 字段是一个数组,包含表达式(包括注释)。我们可以使用通常的 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

抽象语法树

[edit | edit source]

这种在解析代码后表示代码的方式称为 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 和原子(例如符号和数字)组成。

表达式插值

[edit | edit source]

在某种程度上,字符串和表达式是相似的 - 它们可能包含的任何 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

尽管这是一个引用的表达式,因此没有被求值,但 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 条件不适用。宏所做的只是返回 n。但是,如果您传递表达式,则宏中的代码有机会在表达式被求值之前检查和/或处理表达式的内容,使用 .args 字段

julia> @p 3 + 4 - 5 * 6 / 7 % 8
Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)]
2.7142857142857144

在这种情况下,if 条件被触发,传入表达式的参数以未求值的形式打印出来。因此,您可以看到参数作为表达式数组,在被 Julia 解析后但尚未被求值之前。您还可以看到算术运算符的不同优先级是如何在解析操作中被考虑的。注意顶级运算符和子表达式是如何用冒号 (:) 引用。

另外请注意,宏 p 返回了参数,然后对参数进行了求值,因此结果为 2.7142857142857144。但它不必这样做 - 它可以返回一个引用表达式。

例如,内置的 @time 宏返回一个引用表达式,而不是使用 eval() 来评估宏中的表达式。@time 返回的引用表达式在宏完成工作时在 调用上下文中 被评估。以下是定义

macro time(ex)
    quote
        local t0 = time()
        local val = $(esc(ex))
        local t1 = time()
        println("elapsed time: ", t1-t0, " seconds")
        val
    end
end

注意 $(esc(ex)) 表达式。这就是您“转义”要计时代码的方式,该代码位于 ex 中,因此它不会在宏中被评估,而是保持完好无损,直到整个引用表达式被返回到调用上下文并在那里执行。如果这只是 $ex,那么表达式将被插值并立即评估。

如果要将多行表达式传递给宏,请使用 begin ... end 形式

@p begin
    2 + 2 - 3
end
Any[:( # none, line 2:),:((2 + 2) - 3)]
1

(您也可以用类似于调用函数时使用的括号来调用宏,使用括号将参数括起来

julia> @p(2 + 3 + 4 - 5)
Any[:-,:(2 + 3 + 4),5]
4

这将允许您定义接受多个表达式作为参数的宏。)

eval()@eval

[编辑 | 编辑源代码]

有一个 eval() 函数和一个 @eval 宏。您可能想知道两者之间有什么区别?

julia> ex = :(2 + 2)
:(2 + 2) 

julia> eval(ex)
4

julia> @eval ex
:(2 + 2)

函数版本 (eval()) 展开表达式并进行评估。宏版本不会自动展开您提供的表达式,但您可以使用插值语法来评估表达式并将表达式传递给宏。

julia> @eval $(ex)
4

换句话说

julia> @eval $(ex) == eval(ex)
true

这是一个可能需要使用某些自动化来创建一些变量的示例。我们将使用 eval() 创建前十个平方和十个立方

for i in 1:10
   symbolname = Symbol("var_squares_$(i)")
   eval(quote $symbolname = $(i^2) end)
end

这将创建一个名为 var_squares_n 的变量,例如

julia> var_squares_5
25

然后使用 @eval

for i in 1:10
   symbolname = Symbol("var_cubes_$(i)")
   @eval $symbolname = $(i^3)
end

类似地,这将创建一个名为 var_cubes_n 的变量,例如

julia> var_cubes_5
125

一旦您有信心,您可能更喜欢这样写

julia> [@eval $(Symbol("var_squares_$(i)")) = ($i^2) for i in 1:10]

范围和上下文

[编辑 | 编辑源代码]

使用宏时,您必须注意范围问题。在前面的示例中,$(esc(ex)) 语法用于防止表达式在错误的上下文中被评估。这是一个说明这一点的另一个人为的例子。

macro f(x)
    quote
        s = 4
        (s, $(esc(s)))
    end
end

此宏声明一个变量 s,并返回一个引用表达式,其中包含 s 和一个转义版本的 s

现在,在宏外部,声明一个符号 s

julia> s = 0

运行宏

julia> @f 2
(4,0)

您可以看到宏为符号 s 返回了不同的值:第一个是在宏上下文中,值为 4,第二个是 s 的转义版本,在调用上下文中被评估,在调用上下文中 s 的值为 0。从某种意义上说,esc() 保护了 s 的值,因为它不受任何伤害地通过宏。对于更现实的 @time 示例,重要的是,您要计时的表达式不会以任何方式被宏修改。

展开宏

[编辑 | 编辑源代码]

要查看宏在最终执行之前扩展的内容,请使用 macroexpand() 函数。它需要一个引用表达式,其中包含一个或多个宏调用,然后将其扩展为适当的 Julia 代码,以便您可以看到宏在调用时的执行情况。

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

#none, line 1: 是一个文件名和行号引用,在源文件中使用比在使用 REPL 时更有用。)

以下是一个示例。此宏将 dotimes 结构添加到语言中。

macro dotimes(n, body)
    quote
        for i = 1:$(esc(n))
            $(esc(body))
        end
    end
end

使用方法如下

julia> @dotimes 3 println("hi there")
hi there
hi there
hi there

或者,不太可能,像这样

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

如果您对它使用 macroexpand(),您可以看到符号名称发生了什么变化

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 
)

输出如下

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

宏本身的 i 被重命名为 #101#i,以避免与我们传递给它的代码中的原始 i 冲突。

更实用的示例:@until

[编辑 | 编辑源代码]

以下是如何定义一个宏,该宏更有可能在您的代码中发挥作用。

Julia 没有 until condition ... do some stuff ... end 语句。也许您想输入类似以下内容

until x > 100
    println(x)
end

您将能够使用新的 until 宏编写代码,如下所示

until <condition>
    <block_of_stuff>
end

但是,在幕后,实际代码将使用以下结构完成工作

while true
    <block_of_stuff>
    if <condition>
        break
    end
end

这构成了新宏的主体,它将被包含在 quote ... end 块中,如下所示,以便它在评估时执行,但不会在此之前执行

quote
    while true
        <block_of_stuff>
        if <condition>
            break
        end
    end
end

因此,几乎完成的宏代码是这样的

macro until(<condition>, <block_of_stuff>)
    quote
        while true
            <block_of_stuff>
            if <condition>
                break
            end
        end
    end
end

剩下的就是弄清楚如何传入我们用于 <block_of_stuff><condition> 部分的代码。请记住,$(esc(...)) 允许代码以“转义”(即未求值)的方式传递。我们将保护条件和块代码,使其在宏代码运行之前不被评估。

因此,最终的宏定义是这样的

macro until(condition, block)
    quote
        while true
            $(esc(block))
            if $(esc(condition))
                break
            end
        end
    end
end

新的宏使用方法如下

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

julia> x = 5
5

julia> @until x < 1 (println(x); global x -= 1)
5
4
3
2
1

如果您想了解比这里提供的更完整的编译过程解释,请访问“进一步阅读”部分中的链接。

Julia 执行多个“传递”来将您的代码转换为本机汇编代码。如上所述,第一个传递 解析 Julia 代码并构建“表面语法”AST,适合宏操作。第二个传递 降低 这个高级 AST 到一个中间表示形式,该形式由类型推断和代码生成使用。在此中间 AST 格式中,所有宏都已扩展,所有控制流都已转换为显式分支和语句序列。在此阶段,Julia 编译器尝试确定所有变量的类型,以便选择通用函数(可以具有多种方法)的最合适方法。

进一步阅读

[编辑 | 编辑源代码]


华夏公益教科书