Scala 词典排列

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

我一直在从事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);
  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
的一个元素)。

更有趣的问题是第万亿个元素是什么(让我们说一个更大的字母表),因为它迫使你去真正理解模式。作为理解的工具,这段代码可能会更清晰。很好的简洁解决方案;)这有助于我理解代码在做什么,尽管我认为我还不能用这种方式编写代码……不过很快:)