查看以下功能实现可能会很有启发性(尽管它不会按字典顺序产生排列)。
我将List
在下面使用 s 代替String
s,因为它们在函数式编程中无处不在,并且有助于思考“递归解决方案”。一个列表要么是空的Nil
,要么由第一个元素(列表的头部)和剩余的列表(列表的尾部)组成x :: xs
。
现在,为涉及列表的问题准备解决方案的典型“功能性”方法是考虑如何x::xs
从xs
.
在排列的情况下,问题是
假设我们有列表的所有排列,xs
我们如何获得 的所有排列x::xs
?
示例:假设我们有 的所有排列List(1, 2)
,即
List(1, 2)
List(2, 1)
我们如何获得 的所有排列List(0, 1, 2)
,它们是
List(0, 1, 2)
List(1, 0, 2)
List(1, 2, 0)
List(0, 2, 1)
List(2, 0, 1)
List(2, 1, 0)
仔细观察会发现,我们只是0
以所有可能的方式将List(1, 2)
.
因此,如果我们有一个函数可以产生将单个元素插入给定列表的所有可能结果——让我们调用它inserts
——那么解决方案可能如下所示
def permutations[A](xs: List[A]) : List[List[A]] = xs match {
case Nil => List(Nil)
case x::xs => permutations(xs).flatMap(inserts(x, _))
}
也就是说,如果给定列表xs
为空,则只有一种排列,即空列表。否则,我们首先计算xs
(通过递归调用)的所有排列,然后x
以所有可能的方式插入到由此获得的列表中。
生成x
列表中所有可能插入的函数ys
如下所示:
def inserts[A](x: A, ys: List[A]) : List[List[A]] = ys match {
case Nil => List(List(x))
case y::ys => (x::y::ys) :: inserts(x, ys).map(y::_)
}
如果ys
为空,则结果只是单例列表List(x)
。否则,我们再次“递归思考”,即假设我们有 into 的所有可能插入,x
我们ys
如何获得 into 的所有可能x
插入y::ys
?首先,我们y
在所有解决方案的前面添加ys
- - 产生所有以;inserts(x, ys).map(y::_)
开头的解决方案 y
然后我们添加x
第一个元素的解决方案,即x::y::ys
.
注意:当然,此解决方案的结果顺序与 Project Euler 所需的结果顺序不同。这就是为什么最初的帖子中的解决方案“混合”了计算较小列表的排列然后使用它们生成完整列表的排列的两个步骤的原因。这里较小的“列表”是s.filterNot(_ == x)
(它至少比 短一个元素s
,因为x
它是 的元素s
)在递归中使用。