【问题标题】:Code to enumerate permutations in Scala在 Scala 中枚举排列的代码
【发布时间】:2011-11-14 16:10:47
【问题描述】:

我编写了一个函数来枚举给定列表的所有排列。你觉得下面的代码怎么样?

def interleave(x:Int, l:List[Int]):List[List[Int]] = {
  l match { 
    case Nil => List(List(x))
    case (head::tail) =>
      (x :: head :: tail) :: interleave(x, tail).map(head :: _)
  }
}

def permutations(l:List[Int]):List[List[Int]] = {
  l match {
    case Nil => List(List())
    case (head::tail) =>
      for(p0 &lt- permutations(tail); p1 &lt- interleave(head, p0)) yield p1
  }
}

【问题讨论】:

  • 这应该是 codereview.SE。
  • @Raphael 我不得不用谷歌搜索那个,所以这里是给懒惰的codereview.stackexchange.com
  • 我认为 OP 适合 SO。在改进 Scala 编程的过程中,人们需要看看其他人可能会如何处理一些问题,这里是排列。
  • @Icn,不过栈溢出的重点是回答编程的具体问题。这里没有被问及或出现的问题。这篇文章的定义是代码审查——“你觉得下面的代码怎么样?”它最适合 CR。不过迁移它为时已晚。

标签: scala permutation


【解决方案1】:

给定一个 Seq,可以通过调用 permutations 方法进行排列。

scala> List(1,2,3).permutations.mkString("\n")
res3: String = 
List(1, 2, 3)
List(1, 3, 2)
List(2, 1, 3)
List(2, 3, 1)
List(3, 1, 2)
List(3, 2, 1)

另外还有combinations的方法:

scala> List(1,2,3).combinations(2).mkString("\n")
res4: String = 
List(1, 2)
List(1, 3)
List(2, 3)

关于你的实现,我想说三件事:

(1) 好看

(2) 提供一个迭代器(这是允许丢弃元素的标准集合方法)。否则,您可以获得 1000 个列表!可能不适合内存的元素。

scala> val longList = List((1 to 1000):_*)
longList: List[Int] = List(1, 2, 3,...


scala> permutations(longList)
java.lang.OutOfMemoryError: Java heap space
    at scala.collection.immutable.List.$colon$colon(List.scala:67)
    at .interleave(<console>:11)
    at .interleave(<console>:11)
    at .interleave(<console>:11)

(3) 您应该删除重复的排列(正如 Luigi 所观察到的),因为:

scala> permutations(List(1,1,3))
res4: List[List[Int]] = List(List(1, 1, 3), List(1, 1, 3), List(1, 3, 1), List(1, 3, 1), List(3, 1, 1), List(3, 1, 1))

scala> List(1,1,3).permutations.toList
res5: List[List[Int]] = List(List(1, 1, 3), List(1, 3, 1), List(3, 1, 1))

【讨论】:

  • 如果有人(比如我)希望在permutations(n) 中有一个n 参数(用于置换多少),您可以改为使用combinations(n).flatMap(_.permutations)
【解决方案2】:

考虑这里的区别:你的版本

scala> permutations(List(1,1,2)) foreach println
List(1, 1, 2)
List(1, 1, 2)
List(1, 2, 1)
List(1, 2, 1)
List(2, 1, 1)
List(2, 1, 1)

参考版本:

scala> List(1,1,2).permutations foreach println
List(1, 1, 2)
List(1, 2, 1)
List(2, 1, 1)

【讨论】:

  • 哇。如果在不阅读/理解文档的情况下使用参考实现实际上可能会破坏算法。通常,您会期望x.permutations.size == faculty(x.size)
【解决方案3】:

也许这个帖子已经很饱和了,但我想我会把我的解决方案混在一起:

假设没有重复元素:

def permList(l: List[Int]): List[List[Int]] = l match {
   case List(ele) => List(List(ele))
   case list =>
     for {
       i <- List.range(0, list.length)
       p <- permList(list.slice(0, i) ++ list.slice(i + 1, list.length))
     } yield list(i) :: p
}

使用重复元素,防止重复(不那么漂亮):

def permList(l: List[Int]): List[List[Int]] = l match {
  case List(ele) => List(List(ele))
  case list =>
    for {
      i <- List.range(0, list.length)
      val traversedList = list.slice(0, i)
      val nextEle = list(i)
      if !(traversedList contains nextEle)
      p <- permList(traversedList ++ list.slice(i + 1, list.length))
    } yield list(i) :: p
}

考虑到它使用切片和列表上的索引,它可能不是最“列表-y”的,但它相当简洁,并且查看它的方式略有不同。它的工作原理是挑选出列表中的每个元素并计算剩余内容的排列,然后将单个元素连接到这些排列中的每一个。如果有更惯用的方法来做到这一点,我很想听听。

【讨论】:

    【解决方案4】:

    我认为标准库中已经存在这样的函数:Seq.permutations。那么为什么要重新发明轮子呢?

    【讨论】:

      【解决方案5】:

      这是基于span的版本。

      def perms[T](xs: List[T]): List[List[T]] = xs match {
        case List(_) => List(xs)
        case _ => for ( x <- xs
                      ; val (l, r) = xs span { x!= }
                      ; ys <- perms(l ++ r.tail)
                      ) yield x :: ys
      }
      

      【讨论】:

        【解决方案6】:

        我猜你正在练习你的 Scala 编程技能。这是另一个,其想法是将不同的元素作为序列的头部,并通过filter 删除重复。代码的复杂性应该没问题,因为 O(n)+O(n 或者可能是 n^2)+O(n)*P(n-1) 由 O(n)*P(n-1) 主导,其中 P(n) 是置换数,不能改进,.

        def permute(xs:List[Int]):List[List[Int]] = xs match {
          case Nil => List(List())
          case head::tail => {
            val len = xs.length
            val tps = (0 to len-1).map(xs.splitAt(_)).toList.filter(tp => !tp._1.contains(tp._2.head))
            tps.map(tp => permute(tp._1:::tp._2.tail).map(tp._2.head :: _)).flatten
          }
        }
        

        【讨论】:

          【解决方案7】:

          我认为我的解决方案比其他解决方案更好

            def withReplacements(chars: String, n: Int) {
          
                  def internal(path: String, acc: List[String]): List[String] = {
                    if (path.length == n) path :: acc else
                      chars.toList.flatMap {c => internal(path + c, acc)}
          
                  }
          
                  val res = internal("", Nil)
                  println("there are " + res.length + " " + n + "-permutations with replacement for " + chars + " = " + res)
                }                                       //> withReplacements: (chars: String, n: Int)Unit
          
          
          
          
                def noReplacements(chars: String, n: Int) {
                  //val set = chars.groupBy(c => c).map {case (c, list) => (c -> list.length)}.toList
          
                import scala.collection.immutable.Queue
          
                  type Set = Queue[Char]
                  val set = Queue[Char](chars.toList: _*)
          
                  type Result = Queue[String]
          
                  // The idea is that recursions will scan the set with one element excluded.
                  // Queue was chosen to implement the set to enable excluded element to bubble through it.
                  def internal(set: Set, path: String, acc: Result): Result = {
                    if (path.length == n) acc.enqueue(path)
                    else
                      set.foldLeft(acc, set.dequeue){case ((acc, (consumed_el, q)), e) =>
                        (internal(q, consumed_el + path, acc), q.enqueue(consumed_el).dequeue)
                      }. _1
          
                  }
          
                  val res = internal(set, "", Queue.empty)
                  println("there are " + res.length + " " + n + "-permutations without replacement for " + set + " = " + res)
          
                }                                       //> noReplacements: (chars: String, n: Int)Unit
          
          
          
              withReplacements("abc", 2)                    //> there are 9 2-permutations with replacement for abc = List(aa, ab, ac, ba, 
                                                            //| bb, bc, ca, cb, cc)
              noReplacements("abc", 2)                      //> there are 6 2-permutations without replacement for Queue(a, b, c) = Queue(b
                                                            //| a, ca, cb, ab, ac, bc)
          
          
              noReplacements("abc", 3)                      //> there are 6 3-permutations without replacement for Queue(a, b, c) = Queue(c
                                                            //| ba, bca, acb, cab, bac, abc)
          
          
              withReplacements("abc", 3)                    //> there are 27 3-permutations with replacement for abc = List(aaa, aab, aac, 
                                                            //| aba, abb, abc, aca, acb, acc, baa, bab, bac, bba, bbb, bbc, bca, bcb, bcc, 
                                                            //| caa, cab, cac, cba, cbb, cbc, cca, ccb, ccc)
            // you can run with replacements (3 chars, n = 4) but noReplacements will fail for obvious reason -- you cannont combine 3 chars to produce 4
              withReplacements("abc", 4)                    //> there are 81 4-permutations with replacement for abc = List(aaaa, aaab, aaa
                                                            //| c, aaba, aabb, aabc, aaca, aacb, aacc, abaa, abab, abac, abba, abbb, abbc, 
                                                            //| abca, abcb, abcc, acaa, acab, acac, acba, acbb, acbc, acca, accb, accc, baa
                                                            //| a, baab, baac, baba, babb, babc, baca, bacb, bacc, bbaa, bbab, bbac, bbba, 
                                                            //| bbbb, bbbc, bbca, bbcb, bbcc, bcaa, bcab, bcac, bcba, bcbb, bcbc, bcca, bcc
                                                            //| b, bccc, caaa, caab, caac, caba, cabb, cabc, caca, cacb, cacc, cbaa, cbab, 
                                                            //| cbac, cbba, cbbb, cbbc, cbca, cbcb, cbcc, ccaa, ccab, ccac, ccba, ccbb, ccb
                                                            //| c, ccca, cccb, cccc)
          (1 to 3) foreach (u =>   noReplacements("aab", u))//> there are 3 1-permutations without replacement for Queue(a, a, b) = Queue(a
                                                            //| , a, b)
                                                            //| there are 6 2-permutations without replacement for Queue(a, a, b) = Queue(a
                                                            //| a, ba, ba, aa, ab, ab)
                                                            //| there are 6 3-permutations without replacement for Queue(a, a, b) = Queue(b
                                                            //| aa, aba, aba, baa, aab, aab)
          

          这些是相同的 3 行代码,但支持可变排列长度并消除了列表连接。

          我已经使第二个更符合概念(这样可以防止累加器的平面映射合并,这也使它更具尾递归性)并扩展到多集排列,这样你就可以说“aab”、“aba”和“baa”是(彼此的)排列。这个想法是字母“a”可以替换两次而不是无限次(有替换大小写)或仅可用一次(没有替换)。因此,您需要一个计数器,它会告诉您每个字母可以替换多少次。

            // Rewrite with replacement a bit to eliminate flat-map merges.
          
              def norep2(chars: String, n: Int/* = chars.length*/) {
          
              import scala.collection.immutable.Queue
          
                type Set = Queue[Char]
                val set = Queue[Char](chars.toList: _*)
          
                type Result = Queue[String]
          
                  def siblings(set: (Char, Set), offset: Int, path: String, acc: Result): Result = set match {case (bubble, queue) =>
                      val children = descend(queue, path + bubble, acc) // bubble was used, it is not available for children that will produce combinations in other positions
                      if (offset == 0) children else siblings(queue.enqueue(bubble).dequeue, offset - 1, path, children) // siblings will produce different chars at the same position, fetch next char for them
                  }
          
                def descend(set: Set, path: String, acc: Result): Result = {
                  if (path.length == n) acc.enqueue(path) else siblings(set.dequeue, set.size-1, path, acc)
                }
          
                val res = descend(set, "", Queue.empty)
                println("there are " + res.length + " " + n + "-permutations without replacement for " + set + " = " + res)
          
              }                                             //> norep2: (chars: String, n: Int)Unit
          
              assert(norep2("abc", 2) == noReplacements("abc", 2))
                                                            //> there are 6 2-permutations without replacement for Queue(a, b, c) = Queue(a
                                                            //| b, ac, bc, ba, ca, cb)
                                                            //| there are 6 2-permutations without replacement for Queue(a, b, c) = Queue(b
                                                            //| a, ca, cb, ab, ac, bc)
              assert(norep2("abc", 3) == noReplacements("abc", 3))
                                                            //> there are 6 3-permutations without replacement for Queue(a, b, c) = Queue(a
                                                            //| bc, acb, bca, bac, cab, cba)
                                                            //| there are 6 3-permutations without replacement for Queue(a, b, c) = Queue(c
                                                            //| ba, bca, acb, cab, bac, abc)
          
          
              def multisets(chars: String, n: Int/* = chars.length*/) {
          
                import scala.collection.immutable.Queue
          
                type Set = Queue[Bubble]
                type Bubble = (Char, Int)
                type Result = Queue[String]
          
                  def siblings(set: (Bubble, Set), offset: Int, path: String, acc: Result): Result = set match {case ((char, avail), queue) =>
                      val children = descend(if (avail - 1 == 0) queue else queue.enqueue(char -> {avail-1}), path + char, acc) // childern can reuse the symbol while if it is available
                      if (offset == 0) children else siblings(queue.enqueue((char, avail)).dequeue, offset - 1, path, children)
                  }
          
                def descend(set: Set, path: String, acc: Result): Result = {
                  if (path.length == n) acc.enqueue(path) else siblings(set.dequeue, set.size-1, path, acc)
                }
          
                val set = Queue[Bubble]((chars.toList groupBy (c => c) map {case (k, v)  => (k, v.length)}).toList: _*)
                val res = descend(set, "", Queue.empty)
                println("there are " + res.length + " multiset " + n + "-permutations for " + set + " = " + res)
          
              }                                             //> multisets: (chars: String, n: Int)Unit
          
          
          
          assert(multisets("abc", 2)  == norep2("abc", 2))  //> there are 6 multiset 2-permutations for Queue((b,1), (a,1), (c,1)) = Queue(
                                                            //| ba, bc, ac, ab, cb, ca)
                                                            //| there are 6 2-permutations without replacement for Queue(a, b, c) = Queue(a
                                                            //| b, ac, bc, ba, ca, cb)
          assert(multisets("abc", 3)  == norep2("abc", 3))  //> there are 6 multiset 3-permutations for Queue((b,1), (a,1), (c,1)) = Queue(
                                                            //| bac, bca, acb, abc, cba, cab)
                                                            //| there are 6 3-permutations without replacement for Queue(a, b, c) = Queue(a
                                                            //| bc, acb, bca, bac, cab, cba)
          
          assert (multisets("aaab", 2) == multisets2("aaab".toList, 2) )
                                                            //> there are 3 multiset 2-permutations for Queue((b,1), (a,3)) = Queue(ba, ab,
                                                            //|  aa)
                                                            //| there are 3 multiset 2-permutations for Queue((b,1), (a,3)) = List(List(a, 
                                                            //| a), List(b, a), List(a, b))
          multisets("aab", 2)                               //> there are 3 multiset 2-permutations for Queue((b,1), (a,2)) = Queue(ba, ab,
                                                            //|  aa)
          
          multisets("aab", 3)                               //> there are 3 multiset 3-permutations for Queue((b,1), (a,2)) = Queue(baa, ab
                                                            //| a, aab)
          norep2("aab", 3)                                  //> there are 6 3-permutations without replacement for Queue(a, a, b) = Queue(a
                                                            //| ab, aba, aba, aab, baa, baa)
          

          作为泛化,您可以使用多重集函数获得有/无替换。例如,

          //take far more letters than resulting permutation length to emulate withReplacements
          assert(multisets("aaaaabbbbbccccc", 3) == withReplacements("abc", 3))
                                                            //> there are 27 multiset 3-permutations for Queue((b,5), (a,5), (c,5)) = Queue
                                                            //| (bac, bab, baa, bcb, bca, bcc, bba, bbc, bbb, acb, aca, acc, aba, abc, abb,
                                                            //|  aac, aab, aaa, cba, cbc, cbb, cac, cab, caa, ccb, cca, ccc)
                                                            //| there are 27 3-permutations with replacement for abc = List(aaa, aab, aac, 
                                                            //| aba, abb, abc, aca, acb, acc, baa, bab, bac, bba, bbb, bbc, bca, bcb, bcc, 
                                                            //| caa, cab, cac, cba, cbb, cbc, cca, ccb, ccc)
          
          
          //take one letter of each to emulate withoutReplacements
          assert(multisets("aaaaabbbbbccccc", 3) == noReplacements("abc", 3))
                                                            //> there are 27 multiset 3-permutations for Queue((b,5), (a,5), (c,5)) = Queue
                                                            //| (bac, bab, baa, bcb, bca, bcc, bba, bbc, bbb, acb, aca, acc, aba, abc, abb,
                                                            //|  aac, aab, aaa, cba, cbc, cbb, cac, cab, caa, ccb, cca, ccc)
                                                            //| there are 6 3-permutations without replacement for Queue(a, b, c) = Queue(c
                                                            //| ba, bca, acb, cab, bac, abc)
          

          If you are more interested about permutations, you may look at

          【讨论】:

            【解决方案8】:
            def permutator[T](list: List[T]): List[List[T]] = {
            
              def _p(total: Int, list: List[T]): List[List[T]] = {
                if (total == 0) {
                  // End of the recursion tree
                  list.map(List(_))
                } else {
                  // Controlled combinatorial 
                  // explosion while total > 0          
                  for (i <- list;
                       j <- _p(total - 1, list)) 
                    yield { i :: j }
            
                  // It is a recursion tree to generate the 
                  // permutations of the elements
                  // --------------------------------------
                  // total = 0 => _p returns 3 elements (A, B, C) 
                  // total = 1 => _p returns 3 * 3 List(List(A, A)...
                  // total = 2 => _p returns 3 * 3 * 3 elements List(List(A, A, A)...
            
                }
              }
            
              _p(list.length - 1, list)
            }
            
            permutator(List("A", "B", "C"))
            
            // output:
            List(A, A, A),List(A, A, B),List(A, A, C),List(A, B, A),List(A, B, B),
            List(A, B, C),List(A, C, A),List(A, C, B),List(A, C, C),List(B, A, A),
            List(B, A, B),List(B, A, C),List(B, B, A),List(B, B, B),List(B, B, C),
            List(B, C, A),List(B, C, B),List(B, C, C),List(C, A, A),List(C, A, B),
            List(C, A, C),List(C, B, A),List(C, B, B),List(C, B, C),List(C, C, A),
            List(C, C, B),List(C, C, C)
            

            【讨论】:

            • 请解释答案,仅代码答案不是很有帮助。
            【解决方案9】:

            我从 SICP 获得了以下方法。我花了很多时间来理解它。但值得一看,而且很漂亮。递归如何在幕后工作。

            def permutations(list: List[Int]): List[List[Int]] = list match {
              case Nil => Nil
              case List(x) => List(List(x))
              case _ => list
                .flatMap(x => 
                   permutations(list.filterNot(_==x))
                   .map(p => x :: p))
            }
            

            上面的解决方案可以在for循环中翻译如下:

            def perms(list: List[Int]): List[List[Int]] = {
              if (list.size == 1) List(list)
              else for {
                x <- list
                y <- perms(list.filterNot(_ == x))
              } yield x :: y
            }
            

            【讨论】:

            • 相信我,SICP 是了解递归和函数式编程的最佳书籍。运动问题令人震惊。
            【解决方案10】:
            def perms(in:List[Int]):List[List[Int]] = {
              def perms0(in: List[Int], tmp: List[Int]): List[List[Int]] =
                if (in.isEmpty) List(tmp) 
                else in.foldLeft(Nil: List[List[Int]])((acc, el) => perms0(in.filter(en => en != el) , el :: tmp) ++ acc)
              perms0(in, Nil)
            }
            

            我通过下图所示的想法启发了解决方案,该图显示了从空(图根)到所有排列(图叶)的部分排列的构建图:https://medium.com/algorithms-and-leetcode/backtracking-e001561b9f28

            【讨论】:

              【解决方案11】:

              这是一个基于循环概念的实现和一个具有两个元素的置换的简单实现。 它不处理 permute 方法中的重复和堆栈溢出方面

              object ImmuPermute extends App {
                def nextCycle(nums: List[Int]): List[Int] = {
                  nums match {
                    case Nil => Nil
                    case head :: tail => tail :+ head
                  }
                }
                def cycles(nums: List[Int]): List[List[Int]] = {
                  def loop(l: List[Int], acc: List[List[Int]]): List[List[Int]] = {
                    if (acc.size == nums.size)
                      acc
                    else {
                      val next = nextCycle(l)
                      loop(next, next :: acc)
                    }
                  }
                  loop(nums, List(nums))
                }
                def permute(nums: List[Int]): List[List[Int]] = {
                  nums match {
                    case Nil => Nil
                    case head :: Nil => List(List(head))
                    case first :: second :: Nil => List(List(first, second), List(second, first))
                    case _ => {
                      val cycledList = cycles(nums)
                      cycledList.flatMap { cycle =>
                        val h = cycle.head
                        val t = cycle.tail
                        val permutedList = permute(t)
                        permutedList map { pList =>
                          h :: pList
                        }
                      }
                    }
                  }
                }
                val l = permute(List(1, 2, 3, 4))
                l foreach println
                println(l.size)
              }
              

              【讨论】:

                【解决方案12】:

                一个班轮:

                List(1,2,3).permutations.toList

                res1: List[List[Int]] = List(List(1, 2, 3), List(1, 3, 2), List(2, 1, 3), List(2, 3, 1) , 列表(3, 1, 2), 列表(3, 2, 1))

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-07
                  • 1970-01-01
                  • 2014-02-27
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多