2013-08-20 44 views
6

我想写一个类似于List.concat/1的函数,它接受一个列表的枚举并将连续列表作为一个连续的流发送。Lazily连接一个枚举列表

它的工作是这样的:

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的解释将不胜感激!

回答

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相同的参数),然后在调用f1(还原函数)之前调用f.(entry)有效映射元素。注意我们是如何逐个映射元素的。

这样做的平面地图的变体很可能是这样的:

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的一般工作方式。

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]