Lazily连接了一个列表

时间:2013-08-20 17:32:09

标签: stream lazy-evaluation elixir

我想编写一个类似于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的任何解释都将非常感谢!

2 个答案:

答案 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并返回一个新函数,它接收entryacc(相同的参数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]