Do you want to pick up from where you left of?
Take me there

列舉 (Enum)

一組在可列舉函數中的列舉演算法。

列舉 (Enum)

Enum 模組包含超過 70 個可列舉 (enumerables) 的工作函數。 我們在 previous lesson,中了解到的所有群集,除了元組之外,都為可列舉。

這個課程只涵蓋可用函數中的一個子集,但我們其實可以自己去測試它們。 讓我們在 IEx 中做一個小實驗。

iex> Enum.__info__(:functions) |> Enum.each(fn({function, arity}) ->
...>   IO.puts "#{function}/#{arity}"
...> end)
all?/1
all?/2
any?/1
any?/2
at/2
at/3
...

經由上述指令,很明顯的我們有很多函數,而且它們都有明確的存在原因。 列舉法 (Enumeration) 是函數式程式設計的核心,將其與 Elixir 的其他特性結合起來,能夠賦與開發者不可思議的強大能力。

有關函數的完整列表,請參考官方 Enum 文件;惰性列舉 (lazy enumeration) 請使用 Stream 模組。

all?

當使用 all?/2,和眾多 Enum 時,我們提供一個適用於我們群集項目的函數。 在 all?/2 這個例子中,全部的群集必須回傳 true 否則只有 false 將被回傳:

iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 3 end)
false
iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 1 end)
true

any?

不像前述的例子,假設至少有一個項目為 trueany?/2 即回傳 true

iex> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 5 end)
true

chunk_every

如果你需要把群集分成更小的群組,chunk_every/2 就可能是你正在尋找的函數:

iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]

chunk_every/4 有幾個選項,但我們還不會深入了解它,查看 the official documentation of this function 來獲得更多資訊。

chunk_by

如果我們需要根據大小以外的東西對我們的群集進行分組,我們可以使用 chunk_by/2 函數。 它需要一個給定的列舉和一個函數,而當該函數回傳值改變時,一個新群組將被觸發並開始創建下一個:

iex> Enum.chunk_by(["one", "two", "three", "four", "five"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"]]
iex> Enum.chunk_by(["one", "two", "three", "four", "five", "six"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"], ["six"]]

map_every

有時候,分類群集仍無法滿足我們的需求。 在這個例子中 map_every/3,能夠非常有效的對中 (hit) 每一個 nth 項目,且總是準確對中第一個需要被改變的值:

# Apply function every three items
iex> Enum.map_every([1, 2, 3, 4, 5, 6, 7, 8], 3, fn x -> x + 1000 end)
[1001, 2, 3, 1004, 5, 6, 1007, 8]

each

可能有必要迭代 (iterate) 一個群集而不產生新的值,對於這個例子我們使用 each/2

iex> Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok

: each/2 函數回傳 atom :ok

map

將函數應用到每個項目,並產生一個新的群集時,使用 map/2 函數:

iex> Enum.map([0, 1, 2, 3], fn(x) -> x - 1 end)
[-1, 0, 1, 2]

min

min/1 在群集中找到最小值:

iex> Enum.min([5, 3, 0, -1])
-1

min/2 做同樣的事,但萬一列舉是空的,它允許我們指定一個函數來產生最小值。

iex> Enum.min([], fn -> :foo end)
:foo

max

max/1 回傳群集中的最大值:

iex> Enum.max([5, 3, 0, -1])
5

max/2 相對 max/1 就如同 min/2 相對 min/1 一樣:

iex> Enum.max([], fn -> :bar end)
:bar

filter

filter/2 函數使我們有能力篩選集合,留下只包含使用所提供函數且計算後為 true 的那些元素。

iex> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]

reduce

通過 reduce/3 我們可以將我們的群集精煉成單一的值。為此,我們提供一個可選擇的累加器 (10 在這個例子中) 傳遞到我們的函數中;如果沒有提供累加器,則使用列舉中的第一個元素:

iex> Enum.reduce([1, 2, 3], 10, fn(x, acc) -> x + acc end)
16

iex> Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end)
6

iex> Enum.reduce(["a","b","c"], "1", fn(x,acc)-> x <> acc end)
"cba1"

sort

排序群集時有二個排序函數會較為簡易。

sort/1 使用 Erlang Term 排序規則 來確定排序順序:

iex> Enum.sort([5, 6, 1, 3, -1, 4])
[-1, 1, 3, 4, 5, 6]

iex> Enum.sort([:foo, "bar", Enum, -1, 4])
[-1, 4, Enum, :foo, "bar"]

sort/2 則允許我們提供自己的排序函數:

# with our function
iex> Enum.sort([%{:val => 4}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 4}, %{val: 1}]

# without
iex> Enum.sort([%{:count => 4}, %{:count => 1}])
[%{count: 1}, %{count: 4}]

uniq

可以使用 uniq/1 刪除列舉中的重複項目:

iex> Enum.uniq([1, 2, 3, 2, 1, 1, 1, 1, 1])
[1, 2, 3]

uniq_by

uniq_by/2 也從列舉中刪除重複項目,但是它允許提供一個函數來進行唯一性比較。

iex> Enum.uniq_by([%{x: 1, y: 1}, %{x: 2, y: 1}, %{x: 3, y: 3}], fn coord -> coord.y end)
[%{x: 1, y: 1}, %{x: 3, y: 3}]
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!