用于枚举Scala中的排列的代码

时间:2011-11-14 16:10:47

标签: scala permutation

我编写了一个函数来枚举给定列表的所有排列。您如何看待下面的代码?

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 <- permutations(tail); p1 <- interleave(head, p0)) yield p1
  }
}

11 个答案:

答案 0 :(得分:57)

给定一个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)提供迭代器(这是允许丢弃元素的std集合方法)。否则,您可以获得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))

答案 1 :(得分:9)

在此处考虑不同之处:您的版本

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)

答案 2 :(得分:6)

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

答案 3 :(得分:6)

也许这个帖子已经饱和了,但我想我会把我的解决方案放到混合中:

假设没有重复元素:

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
}

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

答案 4 :(得分: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
}

答案 5 :(得分:1)

我猜你正在练习你的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
  }
}

答案 6 :(得分:1)

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

  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)

作为一般化,你可以使用multisets功能获得/不使用替换。例如,

//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

答案 7 :(得分:1)

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)

答案 8 :(得分:1)

我从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
}

答案 9 :(得分:1)

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

答案 10 :(得分:0)

这是一个基于循环概念的实现,以及具有两个元素的permute的简单实现。 它不处理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)
}