Scala 词典排列
我一直在从事Euler项目,并在Scala中遇到了一个解决方案(我一直试图用Scala语言解决它)。我本来打算自己去做,但现在我被这个解决方案是如何工作的迷住了 问题是: 0、1和2的词典排列为: 012、021、102、120、201和210 这个词的第一百万个词典排列是什么 数字0、1、2、3、4、5、6、7、8和9 解决方案:Scala 词典排列,scala,functional-programming,permutation,Scala,Functional Programming,Permutation,我一直在从事Euler项目,并在Scala中遇到了一个解决方案(我一直试图用Scala语言解决它)。我本来打算自己去做,但现在我被这个解决方案是如何工作的迷住了 问题是: 0、1和2的词典排列为: 012、021、102、120、201和210 这个词的第一百万个词典排列是什么 数字0、1、2、3、4、5、6、7、8和9 解决方案: def permutations(s : String) : Seq[String] = { if(s.size == 1) Seq(s); els
def permutations(s : String) : Seq[String] =
{
if(s.size == 1)
Seq(s);
else
s.flatMap(x => permutations(s.filterNot(_ == x)).map(x +));
}
val ans = permutations("0123456789")(1000000 - 1).toLong;
println(ans);
下面是一个必要的等效伪代码来解释您给出的解决方案:
foreach (x: s) { // flatMap in the code
val lst = permutations(all character of s except x) // permutations(s.filterNot(_ == x)) in the code
foreach (permutation: lst) { // map in the code
append(x, permutation) // (x +) in the code
// example: x = "0" and permutation = "21" yield "021"
}
}
这在Scala中是微不足道的:
"0123456789".permutations.drop(999999).next
这一切归结为如何对
s
的排列进行分类:
您可以按排列的第一个字符对排列进行分组。因为第一个字符在这样一个组中是固定的,所以这个组基本上是:第一个字符+所有其他字符的排列。这是算法的归纳步骤。基本步骤是,单个元素只有一个排列
如果您查看Scala代码:
// this is the base step
if(s.size == 1)
Seq(s)
递归步骤如下所示:
- 对于
中的每个字符s
,x
- 计算所有其他变量的排列
- 在它们的开头重新添加
x
- 所有的排列都是从
开始的x
- 将所有这些组连接(展平)在一起
s.flatMap(x => permutations(s.filterNot(_ == x)).map(x + _))
看看下面的函数实现可能会有所启发(尽管它不会产生字典顺序的排列) 我将在下面使用
List
s,而不是String
s,因为它们在函数式编程中无处不在,并且便于思考“递归解决方案”。列表要么为空Nil
,要么由第一个元素(列表的头部)和剩余的列表(列表的尾部)x::xs
现在,为涉及列表的问题制定解决方案的一种典型的“功能性”方法是思考如何从xs
的解决方案中获得非空列表x::xs
的解决方案
在排列的情况下,问题是
假设我们拥有列表xs
的所有排列,我们如何获得x::xs
的所有排列
示例:假设我们有列表(1,2)
的所有排列,即
List(1, 2)
List(2, 1)
我们如何获得列表(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
插入到列表(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)
。否则,我们再次“递归地思考”,即假设我们将x
的所有可能插入ys
,我们如何将x
的所有可能插入y::ys
?首先,我们将y
添加到ys
的所有解决方案前面--插入(x,ys).map(y::)
--生成所有以y
开头的解决方案;然后我们添加解决方案,其中x
是第一个元素,即x::y::ys
注意:当然,此解决方案的结果顺序与Project Euler中要求的结果顺序不同。这就是为什么最初文章中的解决方案“混合”了计算较小列表的排列的两个步骤,然后使用它们生成完整列表的排列。这里较小的“列表”是
s.filterNot(==x)
(它至少比s
短一个元素,因为x
是递归中使用的s
的一个元素)。更有趣的问题是第万亿个元素是什么(让我们说一个更大的字母表),因为它迫使你去真正理解模式。作为理解的工具,这段代码可能会更清晰。很好的简洁解决方案;)这有助于我理解代码在做什么,尽管我认为我还不能用这种方式编写代码……不过很快:)