I am using a functional programming style to solve the Leetcode easy question, Count the Number of Consistent Strings. The premise of this question is simple: count the amount of values for which the predicate of "all values are in another set" holds.
I was able to do this pretty concisely like so:
class Solution {
fun countConsistentStrings(allowed: String, words: Array<String>): Int {
val permitted = allowed.toSet()
return words.count{it.all{it in permitted}}
}
}
I know that Java streams are lazy, but have read Kotlin is only lazy when asSequence
is used and are otherwise eager.
For reductions to a boolean based on a predicate using any
, none
, or all
, it makes the most sense to me that this should be done lazily (e.g. a single false
in all
should evaluate the whole expression to false
and stop evaluating the predicate for other elements).
Are these operations implemented this way, or are they still done eagerly like other operations in Kotlin. If so, there a way to do them lazily?
No, those methods are not lazy.
First, bear in mind that there are multiple methods with each of those names: two defined on Sequence
, two defined on each of thirteen types of array, two on Map
, and one on Iterable
. It's clear you're interested only in those defined on Sequence
, as those other types don't support laziness.
So, let's look at the docs! The docs for Sequence.any()
, for Sequence.none()
, and for Sequence.all()
methods all say:
The operation is terminal.
To confirm what this means, the docs for the kotlin.sequences
package, say:
If the sequence operation returns another sequence, which is produced lazily, it's called intermediate, and otherwise the operation is terminal.
So those methods are not lazy; when executed, they cause the sequence to be evaluated as far as is needed to produce the required value. (However, they don't evaluate it any further than is needed, which may be what you're asking. After all, that's the point of using Sequences!)
(In fact, you can see from their types that there's no way they could be lazy: each of them returns a Boolean
value, which is either true or false. To support lazy evaluation, they'd need to return a Future
or similar object with a getter that could be called to produce a final result. But a Boolean
already is that final result.)
I think you overinterpret what lazily and eagerly mean. Like "eagerly" means to always do everything in the most inefficient way possible.
Lazy collections (Streams API, sequences) try to postpone calculating their contents until necessary. On the other hand regular collections perform operations immediately when requested. But that doesn't mean if we ask a regular collection for its first element, it will iterate over all of them for no reason.
As a matter of fact, these functions are implemented in almost exactly the same way for both iterables and sequences. The difference is in other transformations and operators. Below is an example for any()
:
public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
if (this is Collection && isEmpty()) return false
for (element in this) if (predicate(element)) return true
return false
}
public inline fun <T> Sequence<T>.any(predicate: (T) -> Boolean): Boolean {
for (element in this) if (predicate(element)) return true
return false
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With