Code to enumerate permutations in Scala

后端 未结 11 2023
臣服心动
臣服心动 2020-12-02 16:06

I coded a function to enumerate all permutations of a given list. What do you think of the code below?

def interleave(x:Int, l:List[Int]):List[List[Int]] = {         


        
相关标签:
11条回答
  • 2020-12-02 16:25

    Here is a version based on 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
    }
    
    0 讨论(0)
  • 2020-12-02 16:27

    I think that mine solution is better than others

      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)
    

    These are the same 3 lines of code but variable permutation lengths are supported and list concatenations are eliminated.

    I have made the second more ideomatic (so that flat-map merges of accumulator are prevented, which also makes it more tail-recursive) and extended into multiset permutations, so that you can say that "aab", "aba" and "baa" are permutations (of each other). The idea is that letter "a" is replacable two times instead infinitly (with replacement case) or availble only one time (without replacements). So, you need a counter, which tells you how many times every letter is avaiable for replacement.

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

    As generalizaiton, you can obtain with/without replacements using multisets funciton. For instance,

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

    0 讨论(0)
  • 2020-12-02 16:29

    I got the following approach from SICP. It takes me fare amount of time to understand it. But it's worth and beautiful to see. How recursion works behind the scenes.

    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))
    }
    

    The above solution can be translated in for loop as follows:

    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
    }
    
    0 讨论(0)
  • 2020-12-02 16:30

    Maybe this thread is already well-saturated, but I thought I'd throw my solution into the mix:

    Assuming no repeat elements:

    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
    }
    

    With repeat elements, preventing duplicates (not as pretty):

    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
    }
    

    It's potentially not the most "list-y", given that it uses slice and an index on the list, but it's rather concise and a slightly different way of looking at it. It works by singling out each element in the list and computing the permutations of what's remaining, and then concatenating the single element to each of those permutations. If there's a more idiomatic way to do this, I'd love to hear about it.

    0 讨论(0)
  • 2020-12-02 16:31

    I think such a function already exists in the standard library: Seq.permutations. So why reinventing the wheel again?

    0 讨论(0)
  • 2020-12-02 16:37
    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)
    }
    

    I inspired solution by the idea shown on the picture below showing building graph of partial permutations starting from empty (graph root) toward all permutations (graph leaves): https://medium.com/algorithms-and-leetcode/backtracking-e001561b9f28

    0 讨论(0)
提交回复
热议问题