Reputation: 134
I have the an ArrayList of elements which I need to get Combinations of pairs.
eg. [A, B, C]
will be converted to [[A, B], [A, C], [B, C]]
I currently use the normal way of achieving this
for(i in 0 until arr.size-1)
for(j in i+1 until arr.size)
//do stuff with arr[i], arr[j]
and If I need combinations of more than two elements I'll probably write a recursive function to do the same. My concern is that this method is still old-school & might be not as Functional-Kotlin like
Is there a better way to achieve this & also for more number of elements in combination without going into recursion?
Upvotes: 6
Views: 3489
Reputation: 305
Answers above are really verbose for Kotlin standards. This can be solved using flatmap which much more succinct IMO:
fun main() {
val letters = "ABC".toList();
val pairs = letters
.flatMap {first -> letters
.filter{second -> !first.equals(second)} // if you want to exclude "identity" pairs
.map{second->first to second }};
println(pairs)
}
Upvotes: 2
Reputation: 31
val arr = arrayListOf("A", "B", "C", "D")
val list= mutableListOf<Pair<String, String>>()
arr.indices.forEach() {
i -> arr.indices.minus(0..i).forEach() {
j -> list.add(arr[i] to arr[j]) }
}
println(list)
Output
[(A, B), (A, C), (A, D), (B, C), (B, D), (C, D)]
Upvotes: 2
Reputation: 31
val arr = intArrayOf(1, 2, 3, 4) //test array
arr.indices.forEach() {
i -> arr.indices.minus(0..i).forEach() {
j -> println("${arr[i]} ${arr[j]}") } }
Output
1 3
1 4
2 3
2 4
3 4
Upvotes: 0
Reputation: 1132
So the accepted answer creates pairs. I created an object that works on any length combinations up to the the items.size -1
class CombinationGenerator<T>(private val items: List<T>, choose: Int = 1) : Iterator<List<T>>, Iterable<List<T>> {
private val indices = Array(choose) { it }
private var first = true
init {
if (items.isEmpty() || choose > items.size || choose < 1)
error("list must have more than 'choose' items and 'choose' min is 1")
}
override fun hasNext(): Boolean = indices.filterIndexed { index, it ->
when (index) {
indices.lastIndex -> items.lastIndex > it
else -> indices[index + 1] - 1 > it
}
}.any()
override fun next(): List<T> {
if (!hasNext()) error("AINT NO MORE WHA HAPPEN")
if (!first) {
incrementAndCarry()
} else
first = false
return List(indices.size) { items[indices[it]] }
}
private fun incrementAndCarry() {
var carry = false
var place = indices.lastIndex
do {
carry = if ((place == indices.lastIndex && indices[place] < items.lastIndex)
|| (place != indices.lastIndex && indices[place] < indices[place + 1] - 1)) {
indices[place]++
(place + 1..indices.lastIndex).forEachIndexed { index, i ->
indices[i] = indices[place] + index + 1
}
false
} else
true
place--
} while (carry && place > -1)
}
override fun iterator(): Iterator<List<T>> = this
}
fun main() {
val combGen = CombinationGenerator(listOf(1, 2, 3, 4), 3)
combGen.map { println(it.joinToString()) }
}
Upvotes: 2
Reputation: 23125
One thing you can do to make it more functional is to decouple the pair production from their consumption.
The pair generator could be written with the function sequence
:
fun <T> elementPairs(arr: List<T>): Sequence<Pair<T, T>> = sequence {
for(i in 0 until arr.size-1)
for(j in i+1 until arr.size)
yield(arr[i] to arr[j])
}
Then you can use that sequence and process the pairs in different ways, e.g.
fun main() {
elementPairs(listOf('A', 'B', 'C', 'D')).forEach {
println(it)
}
elementPairs(listOf("apple", "desk", "arc", "density", "array"))
.filter { (a, b) -> a.first() == b.first() }
.forEach { println("Starting with the same letter: $it") }
}
You can try it here: https://pl.kotl.in/dJ9mAiATc
Upvotes: 8