6

我想编写一个类似于 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将不胜感激!

4

2 回答 2

8

Elixir 中的枚举是通过归约函数来表示的。只要您告诉我们如何减少它,我们就可以映射任何结构。

Stream 的整个想法是您可以组合这些归约函数。我们以地图为例:

def map(enumerable, f) do
  Lazy[enumerable: enumerable,
       fun: fn(f1) ->
         fn(entry, acc) ->
           f1.(f.(entry), acc)
         end
       end]
end

你收到一个可枚举的,你想用函数映射每个元素f。惰性版本接收实际的归约函数f1并返回一个新函数,该函数接收entryacc(将接收相同的参数f1),然后在调用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

现在,每次调用 时f.(entry),都会返回一个列表,并且您希望迭代这个新列表的每个元素,而不是迭代整个列表。

我没有尝试过上面的代码(我可能错过了一些细节),但这就是 Streams 的一般工作方式。

于 2013-08-23T10:04:53.710 回答
5

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]
于 2013-08-23T11:19:22.923 回答