2013-08-20 6 views
6

Vorrei scrivere una funzione simile a List.concat/1 che accetta un elenco di elenchi ed emette gli elenchi concatenati come un flusso continuo.Concatenare tranquillamente un elenco di elenchi

che avrebbe funzionato in questo modo:

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] 

Quello che ho elaborato finora è questo:

defmodule MyStream do 
    def concat(lists) do 
    Enumerable.reduce(lists, [], fn(x, acc) -> acC++ x end) 
    end 
end 

Questo produce il risultato corretto, ma, ovviamente, non è pigro.

Ho provato senza successo utilizzando Stream.Lazy ma non riesco davvero a capire il funzionamento interno di esso. Qualsiasi spiegazione su Stream.Lazy sarebbe molto apprezzata!

risposta

8

Gli oggetti in elisir sono rappresentati tramite funzioni di riduzione. Possiamo mappare qualsiasi struttura finché ci dici come ridurlo.

L'intera idea di Stream è che è possibile comporre quelle funzioni di riduzione. Prendiamo mappa come esempio:

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

si riceve un enumerabile e si desidera mappare più di ogni elemento con la funzione f. La versione lazy riceve la funzione di riduzione effettiva f1 e restituisce una nuova funzione, che riceve entry e acc (gli stessi argomenti f1 dovrebbero ricevere) e quindi chiama f.(entry) effettivamente mappando l'elemento prima di chiamare f1 (la funzione di riduzione). Nota come stiamo mappando gli elementi uno per uno.

La mappa variante piatta di questo probabilmente sarebbe qualcosa di simile:

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

Ora, ogni volta che si chiama f.(entry), si ottiene una lista di nuovo e si desidera iterare su ogni elemento di questa nuova lista, invece di scorrere l'elenco nel suo insieme.

Non ho provato il codice sopra (e potrei aver perso qualche dettaglio) ma è così che funzionano gli Stream in generale.

5

Con the help of José Valim era solo un piccolo passo dal suo codice a quello che stavo cercando. Probabilmente ho posto questa domanda piuttosto male, ma quello che stavo veramente cercando era un equivalente alla funzione itertools.chain di Python.

def chain(enumerable) do 
    Stream.Lazy[enumerable: enumerable, 
       fun: fn(f1) -> 
       fn(entry, acc) -> 
        Enumerable.reduce(entry, acc, f1) 
       end 
       end] 
end 

Ciò consente di concatenare enumerabili potenzialmente infiniti di entrambi i flussi o elenchi.

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]