https://kotlinlang.org logo
Title
g

Greg Stepniewski

07/31/2019, 8:06 PM
Is there a way to do this without using mutableList?
fun Example.sequence(vararg operations: Example.() -> Example): List<Example> {
    val sequence = mutableListOf(this)
    operations.forEach { sequence.add(it(sequence.last())) }
    return sequence.toList()
}
s

Shawn

07/31/2019, 8:08 PM
hmm, first thought is
foldWithNext
but that might not quite be what you want
g

Greg Stepniewski

07/31/2019, 8:14 PM
I don't think so. An example usage of this, assuming Example is Int would be:
5.sequence({plus(1)}, {minus(2)}, {times(2)}) // [5, 6, 4, 8]
key here is that I need to provide a list of different operations, and each operation builds on the result of the previous one
s

Shawn

07/31/2019, 8:18 PM
you’ll probably figure this out soon but I’ll take a stab at it later if there isn’t a solution posted
k

karelpeeters

07/31/2019, 8:23 PM
Really ugly solution, might give someone a better idea:
fun <T> T.chainOperations(ops: List<(T) -> T>): List<T> {
    var curr = this
    return listOf(curr) + ops.map { it(curr).also { curr = it } }
}
d

Dominaezzz

07/31/2019, 8:25 PM
Unfortunately yield twice but,
sequence {
    var curr = this
    for (op in operation) {
        yield(curr)
        curr = op(curr)
    }
    yield(curr)
}.toList()
m

Matias Reparaz

07/31/2019, 8:27 PM
fun Example.sequence(vararg operations: Example.() -> Example): List<Example> {
    return operations.fold(listOf(this)) {acc, cur -> acc + (cur(acc.last()))}
}
k

karelpeeters

07/31/2019, 8:28 PM
Looks like this variant of
fold
is called
scan
in other languages.
💯 1
👍 1
@Matias Reparaz The problem with folds like that is the terrible performance, it's unfortunate 😞
g

Greg Stepniewski

07/31/2019, 8:30 PM
@Matias Reparaz I don't think we can get cleaner than what you've done. This is perfect and I thank you 🙂
🤨 2
👍 1
m

Matias Reparaz

07/31/2019, 8:37 PM
I have my doubts about the performance... but if @Greg Stepniewski is looking for a clear implementation I think that my solution can be a good one
d

Dominaezzz

07/31/2019, 8:39 PM
operations.fold(mutableListOf(this)) { acc, cur -> acc += cur(acc.last()) }
might perform a bit better.
k

karelpeeters

07/31/2019, 8:40 PM
You need to actually return something from the lambda though.
d

Dominaezzz

07/31/2019, 8:40 PM
Although, it's still side effecty.
Ah I see.
m

marstran

08/01/2019, 8:05 AM
What about something like this:
fun Example.sequence(vararg operations: Example.() -> Example): List<Example> {
    if (operations.isEmpty()) {
        return listOf(this)
    }
    
    val mappedExample = operations.first()(this)
   
    val nextOperations = operations.drop(1)
    val seq = mappedExample.sequence(*nextOperations.toTypedArray())
    return listOf(this) + seq
}
It could probably be optimized to not create so many intermediate arrays and lists though. It should also probably be made tail recursive.
k

karelpeeters

08/01/2019, 9:21 AM
Remove the vararg, then you can skip the array and do
subList(1)
instead of
drop(1)
. Still terrible performance of course 🙃
d

Dico

08/01/2019, 6:25 PM
tailrec fun <E> E.sequence(ops: List<(E) -> E>, target: MutableList<E> = arrayListOf(), ind: Int = 0): List<E> {
    target += this
    if (ind >= ops.size) return target
    return ops[ind](this).sequence(ops, target, ind+1)
Seems pretty optimal to me
You could replace ops with vararg array
If you want to do it better you can make the function private and delegate to it with the default parameters
k

karelpeeters

08/01/2019, 6:28 PM
Also you can create an arraylist with the correct initial size.
d

Dico

08/01/2019, 6:29 PM
That's a good idea but we're reaching premature optimization territory
k

karelpeeters

08/01/2019, 6:29 PM
Maybe, but if it doesn't cost any effort 🙂