https://kotlinlang.org logo
#getting-started
Title
# getting-started
j

Jonathan Lennox

03/06/2023, 10:35 PM
Hi - I have a collection/list, and I want to return a collection of the indices of the elements of the collection that match a predicate. What's the best Kotlin idiom for this?
c

CLOVIS

03/06/2023, 10:37 PM
You can use indices :
Copy code
val yourCollection = listOf(1, 2, 3)
println(yourCollection.indices)
j

Jonathan Lennox

03/06/2023, 10:39 PM
I can do
Copy code
override val layerIds: Collection<Int> 
       get() = frameInfo?.let {
            val ret = mutableListOf<Int>()
            it.dti.forEachIndexed{ i, dti ->
                if (dti != DTI.NOT_PRESENT)
                    ret.add(i)
            }
            ret
        } ?: run { super.layerIds }
but that feels more procedural than Kotlin wants
@CLOVIS That's not quite what I'm looking for
c

CLOVIS

03/06/2023, 10:40 PM
What part of it is not satisfying to you?
j

Jonathan Lennox

03/06/2023, 10:40 PM
Having an explicit
ret
that I'm explicitly adding to, rather than some map or filter-ish thing
c

CLOVIS

03/06/2023, 10:41 PM
Why do you want to explicitly add to it?
j

Jonathan Lennox

03/06/2023, 10:41 PM
Because I need a collection of the indices, not the elements
c

CLOVIS

03/06/2023, 10:42 PM
yourCollection.indices
gives you an iterable of the indices, not the elements.
j

Jonathan Lennox

03/06/2023, 10:43 PM
Ah, I see -- so then filter on indices based on looking things up in the list. That works, thanks!
Copy code
get() = frameInfo?.let {
            it.dti.indices.filter {i -> it.dti[i] != DTI.NOT_PRESENT }
        } ?: run { super.layerIds }
c

CLOVIS

03/06/2023, 10:45 PM
If your goal is to filter depending on the value, and keep the indices of the results, I would write
Copy code
yourThing.withIndex()
  .filter { (_, it) -> it != DTI.NOT_PRESENT }
  .map { (index, _) -> index }
j

Jonathan Lennox

03/06/2023, 10:45 PM
Doesn't that return the indices in the post-filter list rather than the pre-filter list?
c

CLOVIS

03/06/2023, 10:46 PM
No, the index is added at the start, after that it's just an iterable or
Pair<Int, YourType>
j

Jonathan Lennox

03/06/2023, 10:46 PM
Oh, I see, because withIndex returns a collection of pairs, and that's preserved through the filter, right!
👍 1
c

CLOVIS

03/06/2023, 10:47 PM
Of course you now have pairs that are being created and destroyed, so it's a tad bit more expensive, but in 99% of cases you shouldn't care about it
*it's a
IndexedValue<T>
, not a
Pair<Int, T>
, but well it's basically the same thing
j

Jonathan Lennox

03/06/2023, 10:48 PM
Out of curiosity why is it
withIndex()
with its own method invocation rather than being like
indices
which doesn't have such an invocation?
c

CLOVIS

03/06/2023, 10:52 PM
I don't think there's any strong reason. The general rule is "if it's expensive it should be a function", but there's no rule saying something cheap should be a property. Both of them are lazy iterables, so I think they could both be properties. Outside of proper rules, if I was the one writing the library, I would have named it either
withIndex()
(function) or
indexed
(property). It seems more natural like this (but to my knowledge that's not a written rule).
j

Jonathan Lennox

03/06/2023, 10:54 PM
Ok, that makes sense.
Thank you!
c

CLOVIS

03/06/2023, 10:54 PM
You're welcome 🙂
c

Chris Fillmore

03/06/2023, 11:09 PM
e

ephemient

03/07/2023, 12:56 AM
fold? either you're accumulating an immutable list by adding, which is a bad idea because it's unnecessarily expensive (O(N^2)), or you're adding to a mutable list, in which case fold is unnecessary
I think the most direct way of accomplishing what you want is
Copy code
.mapIndexedNotNull { i, dti -> if (dti != DTI.NOT_PRESENT) i else null }
or
dti.indices.filter { dti[it] != DTI.NOT_PRESENT }
if the input is a
List
c

Chris Fillmore

03/07/2023, 1:23 AM
I will often use fold if I’m doing a combination map/filter, yes with a mutable list. Is there a benefit to taking one approach over another here? (Honest question) because they all seem like only superficially different approaches. I’ve never used
mapIndexedNotNull
but that’s because I usually reach for fold
e

ephemient

03/07/2023, 1:25 AM
what benefit does
fold
have over
Copy code
buildList {
    list.forEachIndexed { i, dti ->
        if (dti != DTI.NOT_PRESENT) add(i)
    }
}
if you're adding to a mutable list?
c

Chris Fillmore

03/07/2023, 1:26 AM
None other than that I’ve never used
buildList
either, lol
Is there a reason not to use fold? Again this seems like just another trivially different way to construct a new list
I can see the argument for using map and filter directly, because you could pass transforms and predicates which already exist, and work on immutable data
e

ephemient

03/07/2023, 1:32 AM
if you mean
Copy code
.foldIndexed(mutableListOf()) { i, acc, dti ->
    if (dti != DTI.NOT_PRESENT) acc.add(i)
    acc
}
the "accumulator" in scope is one which you would normally expect to vary from iteration to iteration in a normal fold, but it doesn't here. it's like an unnecessary
var
in normal code.
(also it returns a mutable list type, whereas the other approaches don't)
c

Chris Fillmore

03/07/2023, 1:34 AM
Re: the last point, I call toList() at the end Yeah good point about the accumulator. I would in this case define this as an extension on List<T, R> so that the caller could never touch the mutable list
I.e I would create an extension
indexesWhere(predicate)
p

phldavies

03/07/2023, 10:56 AM
I've been using the following extension to achieve this:
Copy code
fun <T> Iterable<T>.indicesOf(predicate: (T) -> Boolean) =
        buildSet { this@indicesOf.forEachIndexed { index, it -> if (predicate(it)) add(index) } }
For a more general approach, an extension
Iterable<T>.indicesOf((T) -> Boolean): Iterable<Int>
might be better as you can
toSet()
or
toList()
etc as required (same probably applies to
Sequence<T>.indicesOf((T) -> Boolean): Sequence<Int>
)
e

ephemient

03/07/2023, 11:39 AM
no, the pattern throughout kotlin-stdlib is to return a
List
from
Iterable
transformations (unless you're using one of the
fooTo(destination)
functions). they run immediately and sequentially, having a
Iterable
result doesn't make sense
p

phldavies

03/07/2023, 11:42 AM
fair point on
Iterable<T> -> List<T>
, but the Sequence extension would still apply. My reasoning here for
Set<Int>
was ultimately that my use-cases required checking for existence within the set of matching indices.
e

ephemient

03/07/2023, 11:43 AM
this would fit the pattern:
Copy code
inline fun <T> Iterable<T>.indicesOf(predicate: (T) -> Boolean): List<Int> = indicesOfTo(mutableListOf(), predicate)
inline fun <T, R : MutableCollection<in Int>> Iterable<T>.indicesOfTo(dest: R, predicate: (T) -> Boolean): R {
    for ((i, elem) in this.withIndex()) if (predicate(elem)) dest.add(i)
    return dest
}
and you could get your set by using
.indicesOfTo(mutableSetOf()) { ... }
👍 1
for
Sequence
, sure, although I think that would just not offer much over the
.withIndex().filter().map()
or
.mapIndexedNotNull()
mentioned earlier
👍 1
p

phldavies

03/07/2023, 11:45 AM
agreed - I ended up with this as my extension as an evolution of
.mapIndexedNotNullTo(mutableSetOf<Int>()) { ... }
but could just as easily have used that and cast to
Set<Int>
to hide the mutability.
10 Views