重要: 在 Elixir 中的串列為鏈結串列(linked list)
如上圖所示,鏈結串列 的結構讓 Elixir 的串列有幾個特點:
這種特性在 functional 程式風格中很方便遞迴使用,
假如今天要處理一個串列可以這樣寫
langs = ["Elixir", "Clojure", "Erlang", "Ocaml", "Rust"]
def admire(items) do
case items do
[] ->
IO.puts "遞迴結束"
[head | tail] ->
IO.puts "#{head} 最棒了"
admire(tail)
end
end
admire(langs)
在第一次呼叫時
admire(["Elixir", "Clojure", "Erlang", "Ocaml", "Rust"])
case 裡面進行 pattern matching
case ["Elixir", "Clojure", "Erlang", "Ocaml", "Rust"] do
[] -> # 第一個匹配嘗試
IO.puts "遞迴結束"
[]
與 ["Elixir", "Clojure", "Erlang", "Ocaml", "Rust"]
匹配失敗
換下一個
case ["Elixir", "Clojure", "Erlang", "Ocaml", "Rust"] do
[head | tail] -> # 第二個匹配嘗試
IO.puts "#{head} 最棒了"
admire(tail)
[head | tail]
這個模式會將 list 的第一個值放到 head,剩下的到 tail
接著使用 tail 當作原本的 langs 放進 admire 再執行一次
直到 tail 變成 []
遞迴結束
備註 1: def
應要在模組裡面才能被定義,這邊方便說明忽略
備註 2: 這個函式沒有尾遞迴最佳化,後續介紹函式呼叫時一起說明
備註 3: 鏈結串列 wiki
除了 List 模組內的函式外
更多更常使用的是在 Enum 模組內
另外也有獨立的 hd
與 tl
可以呼叫
hd(langs) #=> "Elixir"
tl(langs) #=> ["Clojure", "Erlang", "Ocaml", "Rust"]
Enum.reverse(langs) #=> ["Rust", "Ocaml", "Erlang", "Clojure", "Elixir"]
Enum.take(langs, 2) #=> ["Elixir", "Clojure"]
List.delete(langs, "Clojure") #=> ["Elixir", "Erlang", "Ocaml", "Rust"]
++
(連接運算子), --
(差集運算子)Elixir 的 Kernel 模組有提供 ++
(連接運算子), --
(差集運算子) 這兩個額外的函式讓我們可以簡化一些寫法
# 連接兩個串列
[1, 2, 3] ++ [4, 5, 6]
#=> [1, 2, 3, 4, 5, 6]
# 可連接空的串列
[] ++ [1, 2, 3]
#=> [1, 2, 3]
# 差集操作
[1, 2, 3, 4, 5] -- [2, 4]
#=> [1, 3, 5]
# 移除重複元素(只移除第一個匹配)
[1, 2, 2, 3, 2] -- [2]
#=> [1, 2, 3, 2] (只移除第一個 2)
# 移除不存在的元素
[1, 2, 3] -- [4, 5]
#=> [1, 2, 3] (沒有變化,不會報錯)
備註 1: Kernel 模組內的函數為不需要呼叫模組也不需要 import 即可直接使用的 Elixir 核心函數群,
之前使用的 +
, -
, div
, 與這次介紹的 hd
, tail
都是 Kernel 模組的成員。
備註 2: 用起來像語法的 def
, defmodule
, |>
其實也是 Kernel 的函式,偏後面的章節會介紹他們實際上是怎麼組成的。