2017-07-30 56 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. 模式匹配比较容易阅读。