Reputation: 159
I am trying to create a function that generates all possible permutations of length n, where the objects being listed are taken non-exhaustively from a set S. I am trying to accomplish this with Kotlin in a functional style.
This is the same question as was asked for Python here: Generating permutations of a given length - Python The answer provided is Python specific and therefore does not help me.
I also found this: https://discuss.kotlinlang.org/t/cartesian-products/343 But have a hard time understanding if this is the same thing I'm trying to do.
I have written a function that comes close to accomplishing the task, but it returns all non-exhaustive permutations of length <= n which is not what I'm after. Here is the code:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>>{
return if (components.isEmpty() || length <= 0 ){
listOf(listOf())
}else{
components.map { x -> nonexhaustivePermutations(length-1, components)
.map { y -> listOf(x) + y } }
.fold(listOf(listOf()), { x, y -> x + y} )
}
}
I would be thankful if someone points out my mistakes or suggests an entirely new approach to the problem.
Upvotes: 2
Views: 378
Reputation: 2492
You can do it this way:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(emptyList())
else nonexhaustivePermutations(length - 1, components)
.flatMap { sub -> components.map { sub + it } }
Now I'll explain how you can fix your solution. First of all, I'll reformat it:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(listOf())
else components.map { elm ->
nonexhaustivePermutations(length - 1, components).map { sub -> listOf(elm) + sub }
}.fold(listOf(listOf())) { result, elm -> result + elm }
The first problem is elm -> nonexhaustivePermutations(length - 1, components)
. Here you call nonexhaustivePermutations
with the same arguments for each elm
on each recursion step. So I suggest swapping components.map
with nonexhaustivePermutations(length - 1, components).map
:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(listOf())
else nonexhaustivePermutations(length - 1, components).map { sub ->
components.map { elm -> listOf(elm) + sub }
}.fold(listOf(listOf())) { result, elm -> result + elm }
But in addition to significantly improving performance, this swapping also reorders the returned elements. It can partly be fixed by replacing listOf(elm) + sub
with sub + elm
:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(listOf())
else nonexhaustivePermutations(length - 1, components).map { sub ->
components.map { elm -> sub + elm }
}.fold(listOf(listOf())) { result, elm -> result + elm }
If you run this method, you will see that it returns permutations in order of length increasing. So short permutations are added first. To be more precise, they are added at the time the list is created. So to get rid of them, fold(listOf(listOf()))
must be replaced with fold(emptyList())
:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(listOf())
else nonexhaustivePermutations(length - 1, components).map { sub ->
components.map { elm -> sub + elm }
}.fold(emptyList()) { result, elm -> result + elm }
This version works properly. But the only thing the last line does is list flatting. And flatting can be combined with mapping by replacing map
with flatMap
:
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>> =
if (components.isEmpty() || length <= 0) listOf(listOf())
else nonexhaustivePermutations(length - 1, components).flatMap { sub ->
components.map { elm -> sub + elm }
}
Upvotes: 2
Reputation: 31503
You did the hard part, now just tak on a filter in the end :-)
fun <T> nonexhaustivePermutations(length: Int, components: List<T>): List<List<T>>{
return if (components.isEmpty() || length <= 0 ){
listOf(listOf())
}else{
components.map { x ->
nonexhaustivePermutations(length-1, components).map { y -> listOf(x) + y }
}.fold(listOf(listOf<T>())) { x, y -> x + y}
}.filter {
it.size == length
}
}
Upvotes: 0