ElixirでリストをEnum.mapで処理する際に、典型的によく起こる、直前の要素値や履歴を参照したい時にどうするかを紹介します。
問題
次のようにリストをEnum.map/2
でパイプライン処理することはよく行うと思います。
list
|> Enum.map(& ...)
|> Enum.map(fn v -> ... end)
...
Enum.map/2
で記述する関数は、要素の値を参照して結果を返す関数を書きます。たとえば、要素の値を2倍したいときには次のようにします。
list
|> Enum.map(& &1 * 2)
...
ここで問題にしたいのは、関数の中で1つ前の要素を参照したい、あるいは、リストのその前の要素値の履歴を参照したいというような場合です。このときには単純な Enum.map/2
ではプログラミングできません。では、どのようにプログラミングしたら良いのでしょうか?
解決例
Enum.scan/3
を用いると、リストの履歴を積み上げたリストを作ってくれます。
iex> Enum.scan(1..5, [], &[&1 | &2])
[[1], [2, 1], [3, 2, 1], [4, 3, 2, 1], [5, 4, 3, 2, 1]]
ただし、逆順に並んでしまうのですよね。そこで、Enum.reverse/1
をかけます。
iex> 1..5 |> Enum.scan([], &[&1 | &2]) |> Enum.map(&Enum.reverse/1)
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]]
素晴らしい。あとは、Enum.reduce/3
を使います。直前の値を用いて計算するような関数の例として、たとえばフィボナッチ数列を求めてみましょう(ただし初項は除く)
iex> 1..10
...> |> Enum.scan([], & [&1 | &2])
...> |> Enum.map(&Enum.reverse/1)
...> |> Enum.map(fn l ->
...> Enum.reduce(l, 0, fn
...> _, {fn1, fn2} -> {fn1 + fn2, fn1}
...> _, f0 -> {1, f0}
...> end)
...> end)
...> |> Enum.map(&elem(&1, 0))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]