【问题标题】:How does this permutations function work (Scala)?这个排列函数如何工作(Scala)?
【发布时间】:2015-01-23 00:50:35
【问题描述】:

我正在查看 Pavel 对 Project Euler 问题 24 的解决方案,但不能完全弄清楚这个函数是如何工作的 - 有人可以解释它在做什么吗?其目的是返回数字 0 到 9 的百万分之一字典排列。

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,但希望能够理解上述内容。)

【问题讨论】:

  • 请注意,如果原始字符串有重复字符,此功能将不起作用。例如ps("11") 是空的,ps("112") 只是{"12", "12"}(甚至没有"21")。

标签: scala permutation


【解决方案1】:

对于给定字符串 s 的每个字母 (flatMap(c => ...)),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

所以,基本上最外层的函数保证每个字母到达第一个位置,第一个递归调用保证第二个位置相同,依此类推。

【讨论】:

  • 我想我不明白为什么这会产生一组排列。
  • 现在清楚多了!我的答案现在是多余的(遗憾的是我在你的编辑之前就开始写了)。
  • 谢谢。发明这个算法的人一定觉得自己像个天才:)
【解决方案2】:

让我们用伪代码写出来:

for each letter in the string
  take that letter out
  find all permutations of what remains
  stick that letter on the front

因为它适用于字符串中的每个字母,所以这实际上是将每个字母依次移动到字符串的前面(这意味着第一个字母可以是存在的任何字母,这是你需要的置换)。由于它是递归工作的,因此余数就是所有剩余的排列。

注意这个算法假设所有的字母都是不同的(因为filterNot是用来移除选中的字母); collections 库中的 permutations 方法不假设这一点。

【讨论】:

    【解决方案3】:

    与此无关,但您可能有兴趣知道您可以计算百万分之一的字典排列,而无需计算任何以前的排列。

    这个想法很简单:对于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)
    }
    

    【讨论】:

    • 谢谢。在发现构建一个算法来计算每个排列在 Java 中非常痛苦之后,我在几个月前编写的 Java 解决方案中发现了这一点。如果您需要大量执行此计算(但不是只缓存一个表!),效率会高得多。
    猜你喜欢
    • 1970-01-01
    • 2013-05-18
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-17
    • 2016-02-22
    • 1970-01-01
    相关资源
    最近更新 更多