Skip to content

Latest commit

 

History

History
266 lines (223 loc) · 6.38 KB

10.2.md

File metadata and controls

266 lines (223 loc) · 6.38 KB

ストリーム - 遅延列挙 遅延評価しない(先行評価)例

> 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を使うことを検討する

  • データが必要になるまで処理を延期したい
  • 大変な数の要素を扱わなければならないが、一度に作成する必要がない