2015-05-16 40 views
8

我有一個列表我要大塊了基於的B結構類型爲A的過渡因此,例如,我有以下的分塊列表該數據分塊成含有2個元素的列表:基於結構類型改變

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

如果輸入要成爲A的所有或所有B的最初,輸出將是不變的,因爲沒有B-> A轉換髮生。

我想Enum.chunk_by/2是要走的路,但我無法弄清楚如何維護上一個元素的上下文以知道何時分割。

這是什麼樣的東西的習慣解決方案?

回答

4

另一種選擇是chunk_by結構類型然後執行另一次合併列表(當該列表包含%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

必須選擇這一個,因爲根據我目前的Elixir知識水平,這是我唯一能夠維持的一個!謝謝! –

4

Enum.chunk_by/2當前不提供對前一個元素的訪問權限,因此在這種情況下我們不能使用Enum.chunk_by/2。我們將不得不退回到reduce/3

所有Enum功能中,reduce/3是最靈活,在內部被使用最多的,如果不是全部的Enum功能。

下面是去生產中給出的數值[ %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 

請注意,我總是在前面加上一個元素的列表。這是因爲在Elixir中添加列表是一項昂貴的操作。

希望這個解決方案有幫助!

5

另一種方法是使用純遞歸:

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