如何将数字列表转换为连续数字列表

时间:2017-07-30 08:18:40

标签: elixir

我正在尝试编写一个可以将数字列表转换为连续数字列表的函数

例如,转换数字列表,例如:

[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]]

也许我在思考这个问题,但我似乎无法在灵药中找到一个好的解决方案。

欣赏正确方向的任何建议或指示。谢谢!

4 个答案:

答案 0 :(得分:7)

我可以看到两种方式:使用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 :(得分:5)

虽然已经发布了两个正确答案,但我更倾向于使用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中的最新整数时,我就把它放在同一个列表中,否则我用这个整数创建一个新的列表。最后,累加器需要反转,其中的每个列表也需要反转。

答案 2 :(得分: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

答案 3 :(得分:1)

另一种解决方案,基于@Dogbert解决方案,但有一些修改

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

输出:

{{1}}

与@Dogbert解决方案相比的一些优势:

  1. 只有一个反向,没有多次部分反转。
  2. 模式匹配更易于阅读。