0

I want to groupBy a list of items by its value, but only if subsequent, and ignore grouping otherwise:

input: val values = listOf("Apple", "Apple", "Grape", "Grape", "Apple", "Cherry", "Cherry", "Grape")

output: {"Apple"=2, "Grape"=2, "Apple"=1, "Cherry"=2, "Grape"=1}

3 Answers3

1

There's no built in option for this in Kotlin - it has to be custom, so there are many different options.

Because you need to keep track of the previous element, to compare the current one against, you need to have some sort of state. To achieve this you could use zipWithNext or windowed to group elements. Or use fold and accumulate the values into a list - removing and adding the last element depending on whether there's a break in the sequence.

To try and keep things a bit more clearer (even if it breaks the norms a bit) I recommend using vars and a single loop. I used the buildList { } DSL, which creates a clear scope for the operation.

val result: List<Pair<String, Int>> = buildList {
  var previousElement: String? = null
  var currentCount: Int = 0

  // iterate over each incoming value
  values.forEach { currentElement: String ->

    // currentElement is new - so increment the count
    currentCount++

    // if we have a break in the sequence...
    if (currentElement != previousElement) {
      // then add the current element and count to our output
      add(currentElement to currentCount)
      // reset the count
      currentCount = 0
    }

    // end this iteration - update 'previous'
    previousElement = currentElement
  }
}

Note that result will match the order of your initial list.

aSemy
  • 5,485
  • 2
  • 25
  • 51
1

For comparison purposes, here's a short but inefficient solution written in the functional style using fold():

fun <E> List<E>.mergeConsecutive(): List<Pair<E, Int>>
    = fold(listOf()) { acc, e ->
        if (acc.isNotEmpty() && acc.last().first == e) {
            val currentTotal = acc.last().second
            acc.dropLast(1) + (e to currentTotal + 1)
        } else
            acc + (e to 1)
    }

The accumulator builds up the list of pairs, incrementing its last entry when we get a duplicate, or appending a new entry when there's a different item. (You could make it slightly shorter by replacing the currentTotal with a call to let(), but that would be even harder to read.)

It uses immutable Lists and Pairs, and so has to create a load of temporary ones as it goes — which makes this pretty inefficient ((²)), and I wouldn't recommend it for production code. But hopefully it's instructive.

gidds
  • 16,558
  • 2
  • 19
  • 26
0

You cloud use MultiValueMap which can has duplicated keys. Since there is no native model you should implement yourself or use the open-source library.

Here is a reference.

Map implementation with duplicate keys

Pemassi
  • 612
  • 1
  • 7
  • 20
  • How would that work in this case, where the input is simply a list of items of the same type, and their order (and in particular, whether identical items are consecutive or separated by different items) is significant? – gidds Jul 19 '22 at 22:42