Hey, my google fu lost me. Is there a built-in lik...
# getting-started
h
Hey, my google fu lost me. Is there a built-in like
.sublist
that doesn't throw an
OutOfBoundsException
and just gives me all it can? So if I say give me 4 elements from index 2 onwards, but the list has only 5 entries it should just give me back index 2, 3 and 4.
The way I see it the easiest way would be to do
Copy code
myList.sublist(n, myList.size-1).take(m)
c
With no intermediary lists:
Copy code
fun <T> List<T>.safeSubList(from: Int, to: Int) =
    subList(from.coerceIn(indices), to.coerceIn(indices.start, indices.last + 1))
it's not beautiful
s
@CLOVIS's solution can be simplified to coercing min to 0 and max to
size
as follows:
Copy code
subList(from.coerceAtLeast(0), to.coerceAtMost(size))
actually that shows mine is broken as well, it shouldn't return any elements at all in this situation
s
Oh, that's right, good catch! I guess if you want the function to handle all possible error cases this should do the trick:
subList(from.coerceIn(0..size), to.coerceAtLeast(max(from, 0)).coerceAtMost(size))
https://pl.kotl.in/DNMKJkDTs
c
I wonder if this could be a useful addition to the stdlib. At least, this is how I personally expected
subList
to behave
h
Yes I also kind of didn't expect
sublist
to throw
The way I have it now:
Copy code
if(n < list.size) {
    list.subList(n, state.stationList.size - 1).take(m)
}
But I'll probably add an extension function with SJ's answer.
g
list.drop(2)
? Or maybe I didn't get the questio
h
I need to specify the start index from which to give an amount of elements.
g
So you skip (drop) first N
h
So
list.drop(n).take(m)
? Does that throw?
g
No
You get empty list at worse case
h
Huh that would also be a cool solution.
c
This does create two temporary lists though.
g
Maybe
list.slice(n..m)
will do, not sure if that throws or not
slice was both throwing and neither a list method 🙂
Copy code
fun <T> List<T>.safeSubList(n: Int, m: Int): List<T> {
    val start  = min(max(n,0), min(m, this.size))
    val end = min(max(start, m), this.size)
    return this.subList(start, end)
}
this still looks messy, as good old messy javascript code won't throw but won't work as expected either
s
List<T>.slice
body looks like this:
Copy code
if (indices.isEmpty()) return listOf()
return this.subList(indices.start, indices.endInclusive + 1).toList()
so it's not safe in case of index bounds, and also creates new list as a result. One other thing to note is that resulting sublist from normal
subList
method reflects changes made to the original list, whereas
drop
,
take
etc create new lists with shallow copy of the data from original list. So what you actually need depends on the context, I would put it like that:
Copy code
Do you want sublist to reflect changes made to original list?
    Yes:
        Do you want implementation to be index safe?
            Yes: proposed `list.safeSubList(from, to)`
            No: `list.subList(from, to)`
    No:
        Do you want implementation to be index safe?
            Yes:
                Is the performance critical?
                    Yes: `list.safeSubList(from, to).toList()` // or even create yet another extension if you want to avoid SubList wrapping, though it should be cheap as it doesn't copy data
                    No: above or `take`/`takeLast` + `drop`/`dropLast` combo
            No: `list.slice(from, to)`
One thing to note is that performance hits coming from intermediate `List`'s resulting from
take
/
drop
etc may not be affecting you much if you're working on relatively small collections. After all I would try to be pragmatic about it and think of it not only about in performance's aspect, but rather in wider context having readability and maintainability in mind (on the one hand dedicated extension can be super handy and make your life easier, but one the other, if you have a codebase with 200 of such micro extensions and use them all over the place then could just be an overkill making reading and writing code harder)
1