ストリーム - 遅延列挙 遅延評価しない(先行評価)例
> c "enum/pipeline.exs"
[1, 3, 7, 13, 21]
[]
> c "enum/longest_line.exs"
formaldehydesulphoxylate
[]
Enumの呼び出しが自己完結しているので、それぞれの呼び出しはコレクションを取り、コレクションを返す コレクションの要素を必要になった時に処理したい (中間結果を格納しておく必要はない) => ストリームを使う
ストリームは組み立て可能な列挙子(enumerator)
> s = Stream.map [1, 3, 5, 7], &(&1 + 1)
#Stream<[enum: [1, 3, 5, 7], funs: [#Function<48.58052446/1 in Stream.map/2>]]>
> Enum.to_list s
[2, 4, 6, 8]
Streamは列挙可能(enumerable)なので、Streamを扱う他の関数に渡せる
> squares = Stream.map [1, 2, 3, 4], &(&1 * &1)
#Stream<[enum: [1, 2, 3, 4], funs: [#Function<48.58052446/1 in Stream.map/2>]]>
> plus_one = Stream.map squares, &(&1 + 1)
#Stream<[
enum: [1, 2, 3, 4],
funs: [#Function<48.58052446/1 in Stream.map/2>,
#Function<48.58052446/1 in Stream.map/2>]
]>
> odds = Stream.filter plus_one, fn x -> rem(x, 2) == 1 end
#Stream<[
enum: [1, 2, 3, 4],
funs: [#Function<48.58052446/1 in Stream.map/2>,
#Function<48.58052446/1 in Stream.map/2>,
#Function<40.58052446/1 in Stream.filter/2>]
]>
> Enum.to_list odds
[5, 17]
enum/stream1.exs
> c "enum/stream1.exs"
[5, 17]
[]
enum/stream2.exs
> c "enum/stream2.exs"
formaldehydesulphoxylate
[]
enum/stream3.exs
> c "enum/stream3.exs"
formaldehydesulphoxylate
[]
良い点:
- 中間結果保存場所不要
- 届いたデータから逐次処理できる
悪い点:
- 実行は2倍遅い
無限ストリーム
Enum
を使った場合
goh@goh% time elixir 10.2_using_enum.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_enum.exs 1.38s user 0.52s system 107% cpu 1.764 total
goh@goh% time elixir 10.2_using_enum.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_enum.exs 1.37s user 0.52s system 108% cpu 1.752 total
goh@goh% time elixir 10.2_using_enum.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_enum.exs 1.40s user 0.50s system 108% cpu 1.740 total
Stream
を使った場合
goh@goh% time elixir 10.2_using_stream.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_stream.exs 0.38s user 0.15s system 125% cpu 0.419 total
goh@goh% time elixir 10.2_using_stream.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_stream.exs 0.36s user 0.14s system 126% cpu 0.392 total
goh@goh% time elixir 10.2_using_stream.exs
[2, 3, 4, 5, 6]
elixir 10.2_using_stream.exs 0.38s user 0.15s system 130% cpu 0.402 total
Stream.cyle 列挙可能なデータを取り、列挙可能なデータを含む無限ストリームを返す 最後の要素まできたら、最初に戻って繰り返す
tream.cycle(~w{green white})\
> |> Stream.zip(1..5)\
> |> Enum.map(fn {class, value} -> ~s{<tr class="#{class}"><td>#{value}</td></tr>\n} end)\
> |> IO.puts
<tr class="green"><td>1</td></tr>
<tr class="white"><td>2</td></tr>
<tr class="green"><td>3</td></tr>
<tr class="white"><td>4</td></tr>
<tr class="green"><td>5</td></tr>
:ok
Stream.repeatedly 関数を引数にとり、新しい値が必要になった時に毎回呼び出す
> Stream.repeatedly(fn -> true end) |> Enum.take(3)
[true, true, true]
> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
Stream.iterate 初期値と関数を取り、関数に直前の値を適用して次の値を生成する 渡す関数は引数を1つ取るもの
> Stream.iterate(0, &(&1 + 1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
> Stream.iterate(2, &(&1 * &1)) |> Enum.take(5)
[2, 4, 16, 256, 65536]
> Stream.iterate([], &([&1])) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]
Stream.unfold 初期値と関数を取り、返す値と関数に渡す値をtupleで返す
Stream.unfold({0, 1}, fn {f1, f2} -> {f1, {f2, f1 + f2}} end) |> Enum.take(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
- 1回目の返す値: 0
- 1回目の渡す値: {1, 0 + 1}
- 2回目の返す値: 1
- 2回目の渡す値: {1, 2}
- 3回目の返す値: 1
- 3回目の渡す値: {2, 1 + 2} :
Stream.iterateの例をStream.unfoldで実装する
> Stream.iterate(0, &(&1 + 1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
> Stream.unfold(0, fn x -> {x, x + 1} end) |> Enum.take(5)
[0, 1, 2, 3, 4]
> Stream.iterate(0, &(&1 + 1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
> Stream.iterate(2, &(&1 * &1)) |> Enum.take(5)
[2, 4, 16, 256, 65536]
> Stream.unfold(2, fn x -> {x, x * x} end) |> Enum.take(5)
[2, 4, 16, 256, 65536]
tream.unfold(2, &({&1, &1 * &1})) |> Enum.take(5)
[2, 4, 16, 256, 65536]
> Stream.iterate([], &([&1])) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]
> Stream.unfold([], &({&1, [&1]})) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]
> Stream.unfold([], fn x -> {x, [x]} end) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]
Stream.resource 第1引数: リソースを開く関数 第2引数: 繰り返し処理の関数 第3引数: リソースを閉じる時の処理の関数
> c "10.2_using_stream_resource.exs"
a, i, u, e, o
ka, ki, ku, ke, ko
[]
goh@goh% iex enum/countdown.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]
warning: an expression is always required on the right side of ->. Please provide a value after ->
enum/countdown.exs:25
Interactive Elixir (1.6.4) - press Ctrl+C to exit (type h() ENTER for help)
> counter = Countdown.timer
#Function<54.58052446/2 in Stream.resource/3>
> printer = counter |> Stream.each(&IO.puts/1)
#Stream<[
enum: #Function<54.58052446/2 in Stream.resource/3>,
funs: [#Function<39.58052446/1 in Stream.each/2>]
]>
> speaker = printer |> Stream.each(&Countdown.say/1)
#Stream<[
enum: #Function<54.58052446/2 in Stream.resource/3>,
funs: [#Function<39.58052446/1 in Stream.each/2>,
#Function<39.58052446/1 in Stream.each/2>]
]>
> speaker |> Enum.take(5)
52
51
50
49
48
["52", "51", "50", "49", "48"]
> speaker |> Enum.take(3)
32
31
30
["32", "31", "30"]
> speaker |> Enum.to_list
11
10
9
8
7
6
5
4
3
2
1
["11", "10", "9", "8", "7", "6", "5", "4", "3", "2", "1"]
実用上のストリーム 以下の場合にStreamを使うことを検討する
- データが必要になるまで処理を延期したい
- 大変な数の要素を扱わなければならないが、一度に作成する必要がない