20

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]] = {
  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
  }
}
Michael
  • 10,185
  • 12
  • 59
  • 110
  • 8
    This should probably be codereview.SE. – Raphael Nov 14 '11 at 17:24
  • 1
    @Raphael I had to google that one so here it is for the lazy http://codereview.stackexchange.com/ – simbo1905 Oct 30 '13 at 20:16
  • 1
    I think the OP is fine for SO. People need to see what others might do with some problems, permutation here, on their way to improve Scala programming. – lcn Dec 11 '13 at 17:55
  • 4
    @Icn, but the point of stack overflow is to answer specific problems about programming. There's no problem asked about or present here. The post by definition is code review -"What do you think of the code below?" It's best suited for CR. Too late to migrate it though. – ChiefTwoPencils Mar 18 '15 at 21:17

12 Answers12

68

Given a Seq, one can already have permutations by invoking the permutations method.

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)

Furthermore there is also a method for combinations:

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

Regarding your implementation I would say three things:

(1) Its good looking

(2) Provide an iterator (which is the std collections approach that allows to discard elements). Otherwise, you can get lists with 1000! elements which may not fit in memory.

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) You should remove duplicated permutations (as observed by Luigi), since :

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))
JaimeJorge
  • 1,885
  • 16
  • 15
  • In case anyone (like me) expected to have an `n` parameter in`permutations(n)` (for how many to permute), you could do `combinations(n).flatMap(_.permutations)` instead. – combinatorist Jun 04 '21 at 20:36
9

Consider the difference here: your version

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)

The reference version:

scala> List(1,1,2).permutations foreach println
List(1, 1, 2)
List(1, 2, 1)
List(2, 1, 1)
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • Wow. The reference implementation might actually break algorithms if used without reading/understanding the doc. Usually, you will expect `x.permutations.size == faculty(x.size)`. – Raphael Nov 14 '11 at 17:32
7

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.

Matthew Saltz
  • 385
  • 4
  • 8
6

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

Peter Schmitz
  • 5,824
  • 4
  • 26
  • 48
5

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
}
Adam Duracz
  • 91
  • 1
  • 2
2

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
}
arun rajput
  • 51
  • 1
  • 6
  • 2
    Believe me SICP is the best book to understand recursion and functional programming. The exercise problems are mind blowing. – arun rajput May 02 '20 at 16:13
1

I guess you are practicing your Scala programming skill. Here is another one, where the idea is to take different elements as head of sequence and repeats are removed through filter. Complexity of the code shall be fine, since O(n)+O(n or maybe n^2)+O(n)*P(n-1) is dominated by O(n)*P(n-1), where P(n) is the permutation number and cannot be improved, .

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
  }
}
lcn
  • 2,239
  • 25
  • 41
1

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

Val
  • 1
  • 8
  • 40
  • 64
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)
uranio
  • 9
  • 2
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)
}

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

ravkar
  • 11
  • 2
0

This is an implementation which is based on the concept of cycle and a trivial implementation of permute with two elements. It does not handle duplicates and stack overflow aspect in the permute method

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)
}
RajV
  • 1
  • 1
0

One liner:

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), List(3, 1, 2), List(3, 2, 1))

moriarty007
  • 2,054
  • 16
  • 20