如何对符合特定条件的列表进行分组

How to group a list of list that matches certain condition

我有一个列表。

[
  [4, 5, 6], 
  [5, 6, 7], 
  [8, 9, 10], 
  [20, 21, 22],
  [22,23,24]
]

如果 [5,6,7] 的 upper_bound(5) 小于或等于 [4,5,6] 的 lower_bound(6) 则将它们放在一起并最终列表应如下所示。

[
  [
    [4, 5, 6], 
    [5, 6, 7]
  ],
  [8, 9, 10],
  [ 
    [20, 21, 22],
    [22,23,24]
  ]

]

我该怎么做?

假设结果列表的数量少于 32,按 Range 分组将有效。

input
|> Enum.reduce(%{}, fn list, acc ->
  {min, max} = Enum.min_max(list)

  acc |> Map.keys() |> Enum.reduce_while(nil, fn 
    mn..mx, _ when min >= mn and min <= mx and max >= mn and max <= mx -> 
      {:halt, Map.update(acc, mn..mx, & [list | &1])}
    mn..mx, _ when min >= mn and min <= mx -> 
      {:halt, acc |> Map.delete(mn..mx) |> Map.put(mn..max, [list | Map.get(acc, mn..mx)])}
    mn..mx, _ when max >= mn and max <= mx ->
      {:halt, acc |> Map.delete(mn..mx) |> Map.put(min..mx, [list | Map.get(acc, mn..mx)])}
    _, _ -> {:cont, nil}
  end) || Map.put(acc, min..max, [list])
end)
|> Map.values()
|> Enum.map(&Enum.reverse/1)

如果列表的预期数量大于32,并且必须保留排序,则解决方案会更麻烦。

此答案假设项目始终是连续的,如您的示例所示,并且没有列表完全包含另一个列表。

首先,最好使用 Ranges,因此我们首先将列表转换为范围。

然后我们可以编写一个递归算法group_ranges/1,它检查每个范围与其前导。如果重叠(使用 Range.disjoint?/2),将其添加到组中。如果不重叠,开始一个新的组。

最后,由于您希望 non-overlapping 个范围独立而不是一组,我们会在分组完成后处理这种情况。如果这是一个严格的要求,您也可以在此阶段将范围转换回列表。

defmodule Example do
  def run do
    [[4, 5, 6], [5, 6, 7], [8, 9, 10], [20, 21, 22], [22, 23, 24]]
    |> Enum.map(fn [head | tail] -> head..List.last(tail) end)
    |> group_ranges()
    |> Enum.map(fn
      [single] -> single
      group -> group
    end)
  end

  def group_ranges([current | rest]), do: do_group_ranges(rest, [current], [])

  defp do_group_ranges([], group, grouped) do
    Enum.reverse([Enum.reverse(group) | grouped])
  end

  defp do_group_ranges([current | rest], [prev | _] = group, grouped) do
    if Range.disjoint?(current, prev) do
      # Create a new group
      do_group_ranges(rest, [current], [Enum.reverse(group) | grouped])
    else
      # Add to existing group
      do_group_ranges(rest, [current | group], grouped)
    end
  end
end

结果:

iex(1)> Example.run
[[4..6, 5..7], 8..10, [20..22, 22..24]]