nicholasnet
nicholasnet

Reputation: 2277

groupByTo return emptySet in Kotlin

I have string like this.

val input = "perm1|0,perm2|2,perm2|1"

Desired output type is

val output: Set<String, Set<Long>>

and desired output value is

{perm1 [], perm2 [1,2] }

Here I need empty set if value is 0. I am using groupByTo like this

val output = input.split(",")
                  .map { it.split("|") }
                  .groupByTo(
                       mutableMapOf(),
                       keySelector = { it[0] },
                       valueTransform = { it[1].toLong()  }
                   )

However the output structure is like this

MutableMap<String, MutableList<Long>> 

and output is

{perm1 [0], perm2 [1,2] }

I am looking for best way to get desired output without using imperative style like this.

val output = mutableMapOf<String, Set<Long>>()
input.split(",").forEach {

    val t = it.split("|")

    if (t[1].contentEquals("0")) {

        output[t[0]] = mutableSetOf()
    }

    if (output.containsKey(t[0]) && !t[1].contentEquals("0")) {

        output[t[0]] = output[t[0]]!! + t[1].toLong()
    }

    if (!output.containsKey(t[0]) && !t[1].contentEquals("0")) {

        output[t[0]] = mutableSetOf()
        output[t[0]] = output[t[0]]!! + t[1].toLong()
    }
}

Upvotes: 2

Views: 681

Answers (3)

Roland
Roland

Reputation: 23242

While the other answer(s) might be easier to grasp, they build immediate lists and maps in between, that are basically discarded right after the next operation. The following tries to omit that using splitToSequence (Sequences) and groupingBy (see Grouping bottom part):

val result: Map<String, Set<Long>> = input.splitToSequence(',')
    .map { it.split('|', limit = 2) }
    .groupingBy { it[0] }
    .fold({ _, _ -> mutableSetOf<Long>() }) { _, accumulator, element ->
      accumulator.also {
        it.add(element[1].toLong()))
      }
    }

You can of course also filter out the addition of 0 in the set with a simple condition in the fold-step:

// alternative fold skipping 0-values, but keeping keys
.fold({ _, _ -> mutableSetOf<Long>() }) { _, accumulator, element ->
  accumulator.also {
    val value = element[1].toLong()
    if (value != 0L)
      it.add(value)
  }
}

Alternatively also aggregating might be ok, but then your result-variable needs to change to Map<String, MutableSet<Long>>:

val result: Map<String, MutableSet<Long>> = // ...
.aggregate { _, accumulator, element, first ->
  (if (first) mutableSetOf<Long>() else accumulator!!).also {
    val value = element[1].toLong()
    if (value != 0L)
      it.add(value)
  }
}

Upvotes: 0

Ryuzaki L
Ryuzaki L

Reputation: 40008

You can simply use mapValues to convert values type from List<Long> to Set<Long>

var res : Map<String, Set<Long>> = input.split(",")
    .map { it.split("|") }
    .groupBy( {it[0]}, {it[1].toLong()} )
    .mapValues { it.value.toSet() }

And of you want to replace list of 0 with empty set you can do it using if-expression

var res : Map<String, Set<Long>> = input.split(",")
    .map { it.split("|") }
    .groupBy( {it[0]}, {it[1].toLong()} )
    .mapValues { if(it.value == listOf<Long>(0))  setOf() else it.value.toSet() }

Upvotes: 5

sidgate
sidgate

Reputation: 15234

Note that you cannot have Set with key-value pair, result will be of type map. Below code gives sorted set in the values.

val result = "perm1|0,perm2|2,perm2|1".split(",")
        .map {
            val split = it.split("|")
            split[0] to split[1].toLong()
        }.groupBy({ it.first }, { it.second })
        .mapValues { it.value.toSortedSet() }
        

Upvotes: 1

Related Questions