Is there a way to accumulate and map in the same operation?


#1

I have ran into this need several times and have not found a good, concise way to do it and want to know am I just missing it or is this something that is missing from the standard library.

The issue is with the need to do a map type operation where the thing you are mapping to is dependent on doing some type of accumulation operation on the values up to that point. There is map which lets you transform, but all you get is the single value to do the transformation on. There are fold and reduce that let you do accumulation operations, but only give you the single final result.

So for example lets say I have

val x = arrayOf(1, 2, 3, 4, 5, 6)

and what I want to is apply some operation(s) on x to arrive at a transformed version where the values are replaced by the values so far. The result would be an equivalent to:

val result = arrayOf(1, 3, 6, 10, 15, 21)

The only somewhat functional way I can see to do it is use a fold where the accumulator type is a pair that has the accumulation value and a collection that you add to.

In my actual case I have a collection of collections and basically want to create pairs made up of the sum of item counts so far to the collection, which will be an index for viewing them as one big collection.

Is there a good way to do something like this or is this something missing in the library?


#2

I would probably use a closure:

val result = {
    var acc = 0
    arrayOf(1, 2, 3, 4, 5, 6).map { acc += it ; acc  }
}()

Or as a one-liner:

val result = { var acc = 0 ; arrayOf(1, 2, 3, 4, 5, 6).map { acc += it ; acc  } }()

It is, however, kind of ugly to turn this into an utility function because the function you pass must return both the mapped result and the new value of the accumulator.


#3

@dalewking, definitely, the scan function could help you here. Unfortunately it’s not in the stdlib yet.
See https://youtrack.jetbrains.com/issue/KT-7657


#4

Certainly getting closer, but I realized for my purposes what I actually needed was more a post increment result than what I actually showed an example of. So the actual result I needed was more like:

val result = arrayOf(0, 1, 3, 6, 10, 15)

which I suppose I could get to by inserting a zero at the front and dropping the last. In reality my actual problem is even more complex. What I am building is a RecyclerView.Adapter implementation that merges a collection of other adapters to look like a single adapter. What I was building is an index from position in the combined view to the adapter that starts at that position. Here is what I ended up with:

val index: NavigableMap<Int, RecyclerView.Adapter<T>>
    = TreeMap<Int, RecyclerView.Adapter<T>>().apply()
    {
        var offset = 0

        adapters.forEach() { put(offset, it); offset += it.itemCount }
    }

It is possible to make a stdlib function to do this in one call with no external mutable state, but it would require a seed value, 3 type parameters and takes 2 transforms that operate on an input value and take a seed value, one returns the new seed and the other the output value. It would be a pretty obscure and complex method, but your scan functions could be implemented as calls to this function.

The signature would look something like this:

fun <I, S, O> Sequence<I>.transform(seed: S, seedTransform: I.(S) -> S,
        outputTransform: I.(S) -> O) : Sequence<O>
{
    TODO()
}

For sequence, this can’t be inlined (but neither is map) except for a transformTo function. As an operation on an array or a list, it could be.

So I am not sure this deserves to be in the stdlib or not.