2013-08-20 75 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]