2015-05-16 12 views
8

ho una lista che voglio pezzo in base a una transizione da tipo struct B alla A. Così, per esempio, ho il seguente:lista Chunking in base al tipo struct cambiare

iex(1)> defmodule A, do: defstruct [] 
{:module, A ... 
iex(2)> defmodule B, do: defstruct [] 
{:module, B ... 
iex(3)> values = [ %A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{} ] 
[%A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{}] 

voglio avere che i dati chunked su in una lista 2 elementi contenente:

[ [ %A{}, %A{}, %B{}, %B{}, %B{} ], [ %A{}, %A{}, %B{} ] ] 

Se l'ingresso dovesse essere tutto di a o B di tutti inizialmente, l'uscita sarebbe invariata, poiché nessuna B-> Si è verificato un transizione.

Immagino che Enum.chunk_by/2 sia la strada da percorrere, ma ho difficoltà a capire come mantenere il contesto dell'elemento precedente per sapere quando dividere.

Che aspetto ha una soluzione idiomatica per qualcosa di simile?

risposta

4

Un'altra alternativa è quella di chunk_by tipo struct poi fare un altro passaggio fusione delle liste (eccetto quando l'elenco contiene %B{}):

def chunk(structs) do 
    structs 
    |> Enum.chunk_by(& &1.__struct__) 
    |> merge() 
end 

# Don't merge when current is %B 
defp merge([[%B{}|_]=h|t]), do: [h|merge(t)] 

# Merge all others 
defp merge([curr, next|t]), do: [curr ++ next|merge(t)] 

# We are done 
defp merge([]), do: [] 
+0

Devo scegliere questo dato che è l'unico che riesco a mantenere dato il mio livello attuale di conoscenza dell'elisir! Grazie! –

4

Enum.chunk_by/2 al momento non fornisce l'accesso all'elemento precedente, quindi in questo caso non è possibile utilizzare Enum.chunk_by/2. Dovremo, ripiegate sulla reduce/3

Di tutte le funzioni Enum, reduce/3 è il più flessibile e viene utilizzato internamente da maggior parte, se non tutti, i Enum funzioni.

Qui di seguito è un modo per andare a produrre l'output desiderato, dati i valori [ %A{}, %A{}, %B{}, %B{}, %B{}, %A{}, %A{}, %B{} ]:

values 
    |> Enum.reduce([[]], fn (elem, acc) -> 
    prev_list = List.first(acc)   
    prev_elem = List.first(prev_list) 
    b_changed_to_a? = fn -> prev_elem.__struct__ == B && elem.__struct__ == A end 

    if is_nil(prev_elem) || !b_changed_to_a?.() do 
     List.replace_at(acc, 0, [elem|prev_list]) 
    else 
     [[elem]|acc]  
    end 
    end) 
    |> Enum.map(&Enum.reverse/1) 
    |> Enum.reverse 

Si noti che ho sempre anteporre un elemento a un elenco. Questo perché aggiungere un elenco in Elixir è un'operazione costosa.

Spero che questa soluzione sia d'aiuto!

5

Un altro approccio è quello di utilizzare puro ricorsione:

def collect_chunks([]), do: [] 
def collect_chunks(list) do 
    {chunk, post_chunk} = collect_chunk(list) 
    [chunk | collect_chunks(post_chunk)] 
end 

defp collect_chunk([]), do: {[], []} 
defp collect_chunk([%B{} = last_element | [%A{} | _] = post_chunk]), do: {[last_element], post_chunk} 
defp collect_chunk([el | rest]) do 
    {remaining_chunk, post_chunk} = collect_chunk(rest) 
    {[el | remaining_chunk], post_chunk} 
end 
Problemi correlati