我正在研究Pavel对Project Euler问题24的解决方案,但是不能弄清楚这个函数是如何工作的 - 有人可以解释它在做什么吗?其目的是返回数字0到9的第100万字典排列。
def ps(s: String): Seq[String] = if(s.size == 1) Seq(s) else
s.flatMap(c => ps(s.filterNot(_ == c)).map(c +))
val r = ps("0123456789")(999999).toLong
据我所知,当输入字符串为长度为1时,该函数将该字符作为Seq返回,我认为接下来会发生的事情是它附加到剩下的唯一其他字符,但我无法真实地想象如何你到达那一点,或者为什么会产生一个排列列表。
(我已经自己解决了这个问题但使用了permutations
方法,这使得它成为一个相当简单的1-liner,但希望能够理解上述内容。)
答案 0 :(得分:7)
对于给定字符串flatMap(c => ...)
的每个字母(s
),ps
通过排列剩余的字母ps(s.filterNot(_ == c))
并在此前面添加所采用的字母来生成排列排列(map(c +)
)。对于单字母字符串的简单情况,它什么都不做(if(s.size == 1) Seq(s)
)。
修改:为什么这样做?
让我们从一个单字母串改组开始:
[a]
-> a # done.
现在有两个字母,我们将任务分成子任务。取出集合中的每个角色,将其置于第一个位置并置换其余角色。
a [b]
-> b
b [a]
-> a
对于三个字母,它是一样的。取每个字符并将其添加到剩余字母的每个子排列之前。
a [b c]
-> b [c]
-> c
-> c [b]
-> b
b [a c]
-> a [c]
-> c
-> c [a]
# ... and so on
所以,基本上最外面的函数保证每个字母都到达第一个位置,第一个递归调用保证第二个位置相同,依此类推。
答案 1 :(得分:4)
让我们用伪代码写出来:
for each letter in the string
take that letter out
find all permutations of what remains
stick that letter on the front
因为它适用于字符串中的每个字母,这实际上是将每个字母依次移动到字符串的前面(这意味着第一个字母可以是任何字母存在,这是你需要的排列)。由于它以递归方式工作,其余部分是每个剩余的排列。
请注意,此算法假设所有字母都不同(因为filterNot
用于删除所选字母);集合库中的permutations方法不会假设这一点。
答案 2 :(得分:2)
与此无关,但您可能有兴趣知道您可以计算百万分之一的词典排列而无需计算任何先前的排字。
这个想法很简单:对于N
个数字,有N!
个排列。这意味着10位数可以产生3628800个排列,9位数可以产生362880个排列,依此类推。鉴于这些信息,我们可以计算下表:
First digit First Permutation Last Permutatation
0 1 362880
1 362881 725760
2 725761 1088640
3 1088641 1451520
4 1451521 1814400
5 1814401 2177280
6 2177281 2540160
7 2540161 2903040
8 2903041 3265920
9 3265921 3628800
所以第一个数字将是2,因为这是1000000的范围。或者,更简单地说,第一个数字是索引(1000000 - 1) / fat(9)
的数字。所以你只需要递归地应用它:
def fat(n: Int) = (2 to n).foldLeft(1)(_*_)
def permN(digits: String, n: Int): String = if (digits.isEmpty) "" else {
val permsPerDigit = fat(digits.length - 1)
val index = (n - 1) / permsPerDigit
val firstDigit = digits(index)
val remainder = digits filterNot (firstDigit ==)
firstDigit + permN(remainder, n - index * permsPerDigit)
}