我想编写一个类似于List.concat / 1的函数,该函数接受可列出的列表并以连续流的形式发出连接列表。
它会像这样工作:
iex> 1..3 |> Stream.map(&([&1])) |> Enum.to_list
[[1], [2], [3]]
iex> 1..3 |> Stream.map(&([&1])) |> MyStream.concat |> Enum.to_list
[1, 2, 3]
到目前为止我想出的是:
defmodule MyStream do
def concat(lists) do
Enumerable.reduce(lists, [], fn(x, acc) -> acc ++ x end)
end
end
这会产生正确的结果,但显然不是懒惰的。
我尝试使用Stream.Lazy
但未成功,但实际上无法理解它的内部工作原理。对Stream.Lazy
的任何解释都将非常感谢!
答案 0 :(得分:8)
Elixir中的Enumerables通过缩减函数表示。只要您告诉我们如何减少它,我们就可以映射任何结构。
Stream的整个想法是你可以组成那些减少功能。我们以地图为例:
def map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
f1.(f.(entry), acc)
end
end]
end
您收到一个可枚举的内容,并希望使用函数f
映射每个元素。延迟版本接收实际的减少函数f1
并返回一个新函数,它接收entry
和acc
(相同的参数f1
将接收),然后调用{{1在调用f.(entry)
(缩减函数)之前有效地映射元素。请注意我们如何逐个映射元素。
平面地图的变体可能类似于:
f1
现在,每次调用def flat_map(enumerable, f) do
Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
Enumerable.reduce(f.(entry), acc, f1)
end
end]
end
时,都会返回一个列表,并希望迭代这个新列表的每个元素,而不是遍历列表整个列表。
我没有尝试过上面的代码(我可能已经错过了一些细节),但这就是Streams的工作方式。
答案 1 :(得分:5)
使用the help of José Valim,从他的代码到我正在寻找的只是一小步。我可能会提出这个问题但是我真正想要的是与Python的itertools.chain
函数等效。
def chain(enumerable) do
Stream.Lazy[enumerable: enumerable,
fun: fn(f1) ->
fn(entry, acc) ->
Enumerable.reduce(entry, acc, f1)
end
end]
end
这允许您链接两个流或列表的潜在无限可枚举。
iex> 1..1000000 |> Stream.map(&(1..(&1))) |> MyModule.chain |> Enum.take(20)
[1, 1, 2, 1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]