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