我正在尝试使用简单的尾递归来检索列表列表的所有排列。该模块如下所示:
defmodule Permutations do
def of([], accumulator) do
accumulator
end
def of([head | tail], accumulator) do
for item <- head, do: of(tail, accumulator ++ [item])
end
end
我的规格如下:
defmodule PermutationsSpec do
use ESpec
alias WaffleEventImporter.Permutations
describe "of/2" do
subject do: Permutations.of(list_list, [])
let :list_list, do: [[1,2,3],[1,2,3],[1,2,3]]
let :expected, do: [
[1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3],
[2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3],
[3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3],
]
it "returns all permutations for the 2 diensional array provided" do
expect(subject) |> to(eq expected)
end
end
end
不幸的是,当递归展开时,嵌套数组是嵌套的。该规范的结果是:
Expected `[[[[1, 1, 1], [1, 1, 2], [1, 1, 3]], [[1, 2, 1], [1, 2, 2],
[1, 2, 3]], [[1, 3, 1], [1, 3, 2], [1, 3, 3]]], [[[2, 1, 1], [2, 1, 2],
[2, 1, 3]], [[2, 2, 1], [2, 2, 2], [2, 2, 3]], [[2, 3, 1], [2, 3, 2],
[2, 3, 3]]], [[[3, 1, 1], [3, 1, 2], [3, 1, 3]], [[3, 2, 1], [3, 2, 2],
[3, 2, 3]], [[3, 3, 1], [3, 3, 2], [3, 3, 3]]]]` to equals (==)
`[[1, 1, 1], [1, 1, 2], [1, 1, 3], [1, 2, 1], [1, 2, 2], [1, 2, 3],
[1, 3, 1], [1, 3, 2], [1, 3, 3], [2, 1, 1], [2, 1, 2], [2, 1, 3],
[2, 2, 1], [2, 2, 2], [2, 2, 3], [2, 3, 1], [2, 3, 2], [2, 3, 3],
[3, 1, 1], [3, 1, 2], [3, 1, 3], [3, 2, 1], [3, 2, 2], [3, 2, 3],
[3, 3, 1], [3, 3, 2], [3, 3, 3]]`, but it doesn't.
我很欣赏有关如何防止嵌套的任何提示。不幸的是,扁平化输出也删除了组合的第一顺序分组。
答案 0 :(得分:1)
以下解决方案有点不寻常。
我看到了你的代码,我记得列表可以用作Monad,通常使用Monad List进行回溯。 Elixir声明以某种方式执行回溯:for
语句。要解决您的问题,您可以执行以下操作:
for i <- [1,2,3], j <- [1,2,3], k <- [1,2,3], do: [i, j, k]
这将生成您在示例中寻找的排列列表。但是,它不是很有活力。当我想到动态时,我通常会想到Elixir宏。如果您可以根据输入创建一个动态生成上述代码的宏,那么它将是理想的:
defmodule Permutation do
@doc """
Does the permutations over a list of lists.
```
> require Permutation
> Permutation.of([[1,2], [1,2]])
[[1, 1], [1, 2], [2, 1], [2, 2]]
```
"""
defmacro of(list) do
quote do: unquote(gen(list))
end
##
# Generates the for statement for the permutation depending on
# the contents of the input list. Starting index for generated
# variables is 0, there are no arrows and the initial result is
# an empty list.
@doc false
def gen(list) do
gen(list, 0, [], [])
end
##
# Generates the for statement for the permutation depending on
# the contents of the input lists.
defp gen([], _, arrows, list) do
gen_for(arrows, list)
end
defp gen([head | tail], index, arrows, list) when is_list(head) do
var = gen_var(index)
arrow = gen_arrow(var, head)
list = gen_list(var, list)
gen(tail, index + 1, [arrow | arrows], list)
end
defp gen(_, _, _, _) do
:error
end
##
# Generates a variable from an index i.e for index 0 generates i0
defp gen_var(index), do: Macro.var(:"i#{inspect index}", __MODULE__)
##
# Generates an arrow for the for statement i.e. i0 <- [1,2,3]
defp gen_arrow(var, source) do
quote do: unquote(var) <- unquote(source)
end
##
# Generates the list from the for statement block: [i1 | [i0]]
defp gen_list(var, list) do
quote do: [unquote(var) | unquote(list)]
end
##
# Generates the for statement i.e.
# for i1 <- [1,2,3], i0 <- [1,2,3], do: [i1 | [i0]]
defp gen_for(arrows, list) do
quote do
for unquote_splicing(arrows) do
unquote(list)
end
end
end
end
我希望这可以帮助您解决问题。有关上述代码的任何问题,请告诉我。
答案 1 :(得分:0)
就我而言,最简单的方法是压扁结果:
def flatten(list, acc \\ []) when is_list(list) do
unless list |> Enum.all?(&is_list(&1)) do
acc ++ [list]
else
list |> Enum.reduce(acc, fn e, acc -> acc ++ flatten(e) end)
end
end
IO.inspect Permutations.of(list_list, []) |> Permutations.flatten
#⇒ desired
由于这不是标准展平,因此它应保持-1级嵌套数组。所有尝试在运行中展平结果都会破坏尾递归。
另一种选择是使用flat_map
+ chunk
:
def of([head | tail], accumulator) do
head |> Enum.flat_map(&of(tail, accumulator ++ [&1]))
end
IO.inspect Permutations.of(list_list, []) |> Enum.chunk(list_list |> Enum.count)
#⇒ desired