Code to enumerate permutations in Scala

后端 未结 11 2054
臣服心动
臣服心动 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: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

提交回复
热议问题