stud
stud

Reputation: 21

Group a collection by n conditions

In Kotlin, what would be a neat (preferably functional) way of grouping elements having n grouping conditions?

For example:

class Item(val level : Int)

Given the list: (Item(1), Item(2), Item(5))

and two grouping conditions:

  1. level > 0 && level < 3
  2. level > 4

The following lists are expected:

  1. listOf(Item(1), Item(2))
  2. listOf(Item(5))

The groupBy function takes only 1 condition argument. Is there any other function that would be helpful?

Upvotes: 2

Views: 1302

Answers (3)

Roland
Roland

Reputation: 23262

Try to filter out all elements which aren't necessary and then either groupBy or partition them, e.g.:

using partition (i.e. you only need 2 lists out of 1):

listSequence()
  .filter { it.level > 0 && it.level != 3 } // it seems you are only interested in levels > 0 && != 3
  .partition { it.level in 1..2 } // partition into items having 0 < level < 3 and the rest
  .run(::println) // prints: [[Item(level=1), Item(level=2)], [Item(level=5)]] (which is a pair of lists)

using groupBy similar to what Willi Mentzel has shown:

listSequence()
    .filter { it.level > 0 && it.level != 3 } // if you need to filter... otherwise skip that and assign just a random group
    .groupBy { 
       when (it.level) {
         in 1..2 -> 0
         else -> 1
       }
    }
    .values.run(::println) // which also prints ([Item(level=1), Item(level=2)], [Item(level=5)]) but now is a collection of lists

In both cases I used a sequence as follows:

fun listSequence() = sequenceOf(Item(1), Item(2), Item(5), Item(-4), Item(0))

Depends what you want to accomplish in the end... You may also be interested in some of the other available collection functions.

Upvotes: 0

s1m0nw1
s1m0nw1

Reputation: 81929

I'd also utilize partion, as already suggested, here. You could also chain them:

val cond1: (Item) -> Boolean = { it.level in 0..2 }
val cond2: (Item) -> Boolean = { it.level > 4 }
val parts = elements
    .partition { cond1(it) || cond2(it) }
    .first.partition { cond1(it) }
println(parts)

This will result into iterations of your input which is slightly less efficient than the groupBy. Still a linear runtime complexity.

Upvotes: 0

Willi Mentzel
Willi Mentzel

Reputation: 29844

You could return an Int in the lambda passed to groupBy which identifies your criteria. This would work for any number of conditions.

val l = listOf(Item(1), Item(2), Item(5))

val g = l.groupBy {
    when {
        it.level > 0 && it.level < 3 -> 0
        it.level > 4 -> 1
        // ...
        else -> null
    }
}.filterKeys { it != null }) // optional: filter out null as default key

Result:

{0=[Item(level=1), Item(level=2)], 1=[Item(level=5)]}

Upvotes: 6

Related Questions