I've got a use case where I iterate over a sequenc...
# announcements
b
I've got a use case where I iterate over a sequence of numbers to find the max, but I'd like to "quit early" if I find a value over a certain number and just return that as the max, is there a good way to do this? I could call
.any { it > bigEnoughNum }
first, but then if that returns false I have to iterate again to find the actual max, which doesn't seem efficient.
n
iterable.first { it > bigEnoughNum }
?
b
Is that different from the
any
method? If it fails I'd still have to iterate over the whole list to find the max.
n
it returns the thing that was > bigEnoughNum
or throws if there is none...but you can use
firstOrNull
or
find
the same exact way
b
Ah, yes in my case I don't necessarily care about the specific value that's
> bigEnoughNum
e
it's an inline function, so (depending on the structure of your code)
Copy code
iterable.maxByOrNull {
    if (it > bigEnoughNum) return
    it
}
will break early
b
Good call...I thought about just doing a return but I was just thinking the local return wouldn't do much...so I could move this to another method and do that.
n
or use
first
? I'm still unclear why that doesn't work
n
first doesn't work because if no numbers are larger than bigEnoughNum
you have to do a second pass
to get the max
hmm i was thinking maxByOrnull could be used without refactoring but it's surprisingly tricky for type inference reasons
either that or I'm making a silly mistake
Copy code
val x = listOf(1,10,11)
    println(x.maxByOrNull { if (it > 8) return@maxByOrNull it else it })
I expected that to print 10, but it prints 11
b
Pretty sure
return@maxByOrNull
is the same as the normal return that happens there
So you'd need some other label, but I don't think that'd work with a sequence chain (don't think you can add a label there), so you'd have to do it in an enclosing method I think
n
Copy code
println(run { x.maxByOrNull { if (it > 8) return@run it else it }!! })
that'll do it
It's not the same btw
actuall I'm wrong
It's the same 🙂
So yeah, the run thing is the same, though at that point it might be messy enough to turn into a one liner anyway
b
Yeah
n
Copy code
fun <T: Comparable<T>> Iterable<T>.maxOrOver(threshold: T) = this.maxByOrNull { if (it > threshold) return it else it}!!
👍 1
e
more general could be
Copy code
inline fun <T: Comparable<T>> Iterable<T>.firstOrMaxOrNull(
    crossinline predicate: (T) -> Boolean
): T? = maxByOrNull { if (predicate(it)) return it else it }
not sure how general it should be, though. seems pretty specialized
s
What about using the takeWhile/takeUntil function?
b
Oh, using that to 'short circuit' the sequence?
👌 1
That's a good idea...looks like that works
e
constructs a new list… but if that's acceptable then go for whatever is the clearest code for you
b
Is that true in the sequence case?
n
prob not but to use it on a sequence you'd need to do asSequence first which is annoying
It would be really nice if kotlin supported some form of "or" constraints on generics
it's so common to write functions that would work with either iterables or sequences
AFAICS you have to implement them twice basically
e
on JVM the compiler would either have to output multiple implementations or silently use reflection
Scala has union types and its approach is the latter
but also - iterable and sequence operations are significantly different in Kotlin
iterable operations are inline (they're all immediate, so early return etc. work), sequence operations are out-of-line (they're all lazy, so early return is impossible)
n
Yes, I understand, but most likely when you implement the two it would defer down to other functions that had been implemented for both
in this example, maxByOrNull is already implemented in terms of both sequence or iterable
so you would write your function constrained to either sequence or iterable, with a single implementation which just calls maxByOrNull, which gets dispatched correctly based on which constraint is met
I've experienced this before; there's lots of useful functions you can write where the implementation for iterable and sequence is the same character by character, because your implementation is just calling down to things like
map
which are implemented for both (but with the differences you mentioned)
I guess one tricky thing is that you'd maybe want the iterable version to be inline but not the sequence. but if you can live with it not being inline 🤷