2017-07-30 71 views
0

我想寫,可以改變號碼列表爲連續號碼清單功能如何變換號碼列表爲連續號碼清單

例如,轉化一批名單像這樣:

[1, 2, 3, 4, 10, 11, 12, 20, 21, 30, 32, 42, 43, 44, 45, 48, 49] 

到連續的號碼清單像這樣:

[[1, 2, 3, 4], [10, 11, 12], [20, 21], [30], [32], [42, 43, 44, 45], [48, 49]] 

也許我該得太多,但我似乎無法拿出一個很好的解決方案仙丹。

欣賞任何建議或指引在正確的方向。謝謝!

回答

5

我可以看到兩種方法:使用在1.5.0中引入的Enum.chunk_while或使用手邊遞歸。

下面是使用Enum.chunk_while版本:

chunk_fun = fn 
    elem, [] -> {:cont, [elem]} 
    elem, [prev | _] = acc when prev + 1 == elem -> {:cont, [elem | acc]} 
    elem, acc -> {:cont, Enum.reverse(acc), [elem]} 
end 
after_fun = fn 
[] -> {:cont, []} 
acc -> {:cont, Enum.reverse(acc), []} 
end 
Enum.chunk_while(list, [], chunk_fun, after_fun) 

而且這裏有一個由手遞歸版本:

def chunk_cont([]), do: [] 
def chunk_cont([elem | list]), do: chunk_cont(list, elem, []) 

defp chunk_cont([], elem, acc), do: [Enum.reverse(acc, [elem])] 
defp chunk_cont([elem | list], prev, acc) when prev + 1 == elem do 
    chunk_cont(list, elem, [prev | acc]) 
end 
defp chunk_cont([elem | list], prev, acc) do 
    [Enum.reverse(acc, [prev]) | chunk_cont(list, elem, [])] 
end 

兩個版本都做同樣的事情。它們遍歷列表並將當前元素與前一個元素進行比較。如果當前元素是一個「下一個」元素,我們將它推到累加器上,否則我們反轉並放出累加器,並用新累加器繼續迭代。一旦完成,我們仍然可以在累加器中留下一些東西,如果這樣我們發出最後一個元素。

+1

感謝您的解決方案。我不知道你也可以在匿名函數上使用模式匹配。我今天學了些新東西。 –

1

這裏只用尾遞歸的另一種方法,但事實證明相當複雜:

def chunk_cont([hd | rest]) do 
    do_chunk_cont(rest, hd, [[hd]]) 
end 

defp do_chunk_cont([hd | rest], last, [group | acc_rest]) when hd == last + 1 do 
    do_chunk_cont(rest, hd, [[hd | group] | acc_rest]) 
end 
defp do_chunk_cont([hd | rest], _last, [group | acc_rest]) do 
    do_chunk_cont(rest, hd, [[hd] | [Enum.reverse(group) | acc_rest]]) 
end 
defp do_chunk_cont([], _last, [group | acc_rest]) do 
    [Enum.reverse(group) | acc_rest] 
    |> Enum.reverse() 
end 
4

雖然目前已經發布了兩個正確答案,我喜歡用Enum.reduce/3了明確的遞歸如果可能,我相信這可能是比已發佈的基於Enum.chunk_while/4的解決方案稍微更優雅。

[1, 2, 3, 4, 10, 11, 12, 20, 21, 30, 32, 42, 43, 44, 45, 48, 49] 
|> Enum.reduce([], fn 
    x, [] -> [[x]] 
    x, [head = [h | _] | tail] when x == h + 1 -> [[x | head] | tail] 
    x, [head | tail] -> [[x], head | tail] 
end) 
|> Enum.map(&Enum.reverse/1) 
|> Enum.reverse 
|> IO.inspect(charlists: :as_integers) 

輸出:

[[1, 2, 3, 4], [10, 11, 12], [20, 21], [30], [32], [42, 43, 44, 45], [48, 49]] 

的核心思想是這樣的:我先建立一個空列表作爲累加器。每當一個整數等於累加器+ 1中的最新整數時,我把它放在同一個列表中,否則我用該整數創建一個新列表。最後,累加器需要顛倒,其中的每個列表也需要顛倒。

+0

這是一個非常優雅的解決方案。謝謝。此外,似乎我們正在添加到列表的頭部,因爲該列表本質上是一個鏈接列表下的引擎蓋? –

+0

是的,這使得它更高效,並且允許我們使用模式匹配來匹配最近插入的值。由於一切都是相反的,所以我們需要在最後把所有東西都倒過來。你會在Elixir中看到這種模式。 – Dogbert

1

另一種解決方案,是根據@Dogbert解決方案,但有一些修改

[1, 2, 3, 4, 10, 11, 12, 20, 21, 30, 32, 42, 43, 44, 45, 48, 49] 
|> Enum.reverse() 
|> Enum.reduce([], fn 
    x, [head = [h | _] | tail] when x == h - 1 -> [[x | head] | tail] 
    x, acc -> [[x] | acc] 
end) 
|> IO.inspect(charlists: :as_integers) 

輸出:

[[1, 2, 3, 4], [10, 11, 12], [20, 21], [30], [32], [42, 43, 44, 45], [48, 49]] 

在@Dogbert溶液一些優點:

  1. 只有一個相反,沒有很多部分逆轉。
  2. 模式匹配比較容易閱讀。