Is there a way to do this without using mutableLis...
# getting-started
g
Is there a way to do this without using mutableList?
Copy code
fun Example.sequence(vararg operations: Example.() -> Example): List<Example> {
    val sequence = mutableListOf(this)
    operations.forEach { sequence.add(it(sequence.last())) }
    return sequence.toList()
}
s
hmm, first thought is
foldWithNext
but that might not quite be what you want
g
I don't think so. An example usage of this, assuming Example is Int would be:
Copy code
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
you’ll probably figure this out soon but I’ll take a stab at it later if there isn’t a solution posted
k
Really ugly solution, might give someone a better idea:
Copy code
fun <T> T.chainOperations(ops: List<(T) -> T>): List<T> {
    var curr = this
    return listOf(curr) + ops.map { it(curr).also { curr = it } }
}
d
Unfortunately yield twice but,
Copy code
sequence {
    var curr = this
    for (op in operation) {
        yield(curr)
        curr = op(curr)
    }
    yield(curr)
}.toList()
m
Copy code
fun Example.sequence(vararg operations: Example.() -> Example): List<Example> {
    return operations.fold(listOf(this)) {acc, cur -> acc + (cur(acc.last()))}
}
k
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
@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
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
operations.fold(mutableListOf(this)) { acc, cur -> acc += cur(acc.last()) }
might perform a bit better.
k
You need to actually return something from the lambda though.
d
Although, it's still side effecty.
Ah I see.
m
What about something like this:
Copy code
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
Remove the vararg, then you can skip the array and do
subList(1)
instead of
drop(1)
. Still terrible performance of course 🙃
d
Copy code
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
Also you can create an arraylist with the correct initial size.
d
That's a good idea but we're reaching premature optimization territory
k
Maybe, but if it doesn't cost any effort 🙂