I need to map a list and retrieve the first non null element, and I need the map operation to be short circuited like it should be in Java 8 streams API. Is there a ready way to do this in Kotlin, without Java 8 streams?
I created my own extension method to do this:
fun <T, R> Iterable<T>.firstNonNullMapping(transform: (T) -> R?): R? {
for (element in this) {
val result = transform(element)
if (result != null) {
return result
}
}
return null
}
A test proves that this works
val firstNonNullMapping = listOf(null, 'a', 'b')
.firstNonNullMapping {
assertNotEquals(it, 'b') // Mapping should be stopped before reaching 'b'
it
}
assertEquals(firstNonNullMapping, 'a')
IntelliJ, however, suggest that I replace my for loop with the much neater
return this
.map { transform(it) }
.firstOrNull { it != null }
Problem is that this will map all elements of the iterable, and it is essential to my use case that is stops at the first non null element.
Kotlin has lazily evaluated sequences that correspond to Java 8 streams, instead of invoking stream() on a collection, you invoke asSequence():
return this
.asSequence()
.mapNotNull { transform(it) }
.firstOrNull()
Kotlin 1.5 provides a shortcut replacement for the combination of
.asSequence(), .mapNotNull { ... } and .first/firstOrNull(): the functions firstNotNullOf and firstNotNullOfOrNull respectively.
They execute the transform function once for each element and stop as soon as they encounter the first non-null result of that function, so they are short-circuiting.
I wonder what is faster, the #ingoKegel's solution, or this:
return this.firstOrNull { transform(it) != null }?.let { transform(it) }
Related
Consider this nice utility extension function i wanted to use :
inline infix fun <T> T?.otherwise(other: () -> Unit): T? {
if (this != null) return this
other()
return null
}
It could be very useful for logging stuff when expressions evaluated to null for example:
val x: Any? = null
x?.let { doSomeStuff() } otherwise {Log.d(TAG,"Otherwise happened")}
but I see that it wont work for :
val x: Any? = null
x?.otherwise {Log.d(TAG,"Otherwise happened")}
see here for running example
Well when thinking about it i guess that makes sense that if x is null the ? makes the postfix not be executed, but i dont understand why the let in the first example is any different?
Is it possible to fix the utility to be more robust and work without having to have let in the chain?
First, you can simplify the implementation:
inline infix fun <T> T?.otherwise(other: () -> Unit): T? {
if (this == null) { other() }
return this
}
Or
inline infix fun <T> T?.otherwise(other: () -> Unit): T? =
also { if (it == null) other() }
When you do this:
null?.otherwise { println("Otherwise happened") }
?. means "execute if not null", so otherwise is not executed.
What you need to write is:
null otherwise { println("Otherwise happened") }
Note this is very similar to the ?: operator (as Vadik pointed out in the comments):
null ?: println("Otherwise happened")
The difference is that otherwise always returns the value on the left (the same as also), but ?: returns the value on the right when the value on the left is null.
In my opinion, otherwise is confusing, especially as it always returns the left value despite the name. You would be better to use the ?: operator. Or perhaps rename it to something like alsoIfNull.
The let example executes because, when you don't utilize the infix feature, it looks like this:
x?.let {}.otherwise {println("1")}
Notice that it's not ?.otherwise; therefore, it always executes.
So to use otherwise without let, you can omit the ?.
x.otherwise { ... }
x?.let { doSomeStuff() }.otherwise {Log.d(TAG,"Otherwise happened")}
// ⬇️
val value = if (x != null) {
doSomeStuff()
} else {
null
}
value.otherwise {Log.d(TAG,"Otherwise happened")}
x?.otherwise { Log.d(TAG,"Otherwise happened") }
// ⬇️
if (x != null) {
otherwise { Log.d(TAG,"Otherwise happened") }
} else {
null
}
?. means if the value is not null then execute the method and return the result otherwise return null
If I have the following array:
[1,1,1,2,2,1,1,1,1,2,2,3]
Is there any built in method in Kotlin which will filter out adjacent elements of the same value, resulting in:
[1,2,1,2,3]
It's important that the order is preserved.
P.S. My actual use case isn't integers, it's an object which implements equals.
I don't think there is a standard function to do this.
But it is easy to build one with mapOrNull:
fun <T : Any> Iterable<T>.removeAdjacent(): List<T> {
var last: T? = null
return mapNotNull {
if (it == last) {
null
} else {
last = it
it
}
}
}
There's a one-line solution, using zipWithNext():
list.zipWithNext().filter{ it.first != it.second }.map{ it.first } + list.last()
That creates a list of pairs of adjacent elements; we then filter out the identical pairs, and take the first of each remaining pair. That will have omitted the last one, so we have to add that in separately.
That works with any element type, using the object's own notion of equality (via its equals() method); this includes nullable types (unlike another answer). And it's stateless so ‘pure’ functional (which you may or may not consider a good thing!).
It handles one-element lists, but not empty lists; for completeness, you'd have to handle those separately. And it would fit very neatly into an extension function:
fun <T> List<T>.compress() = when (isEmpty()) {
true -> listOf()
else -> zipWithNext().filter{ it.first != it.second }.map{ it.first } + last()
}
Functional solution using fold:
val result = listOf(1,1,1,2,2,1,1,1,1,2,2,3)
.fold(mutableListOf<Int>()) { currentList, currentItem ->
if (currentList.isEmpty()) { // Applies only to the very first item
mutableListOf(currentItem)
} else {
if (currentItem != currentList.last()) {
currentList.apply { add(currentItem) }
} else {
currentList
}
}
}
How can I remove the !! operator when I have the following code?
val flux = Flux.just(Foo("Big"), Foo(null), Foo("Small"))
flux.filter { it.name != null }
.map { functionThatRequiresAString(it.name!!) }
fun functionThatRequiresAString(name: String){
// map
}
There's a handy mapNotNull function, which you can use in conjunction with the safe call operator ? to give:
flux.mapNotNull { it.name?.toUpperCase() }
This give the required output of BIG, SMALL
Kotlin offers quite a few lovely methods for dealing with nullable values. One that comes to my mind is let:
flux
.filter { it.name != null }
.map {it?.let { it1 -> it1.toUpperCase() }
You can combine filter and map through flatMapIterable, using 0 elements when you need to skip something and 1 when you need to map it. Here
flux.flatMapIterable {
val name = it.name
if (name != null) listOf(functionThatRequiresAString(name)) else listOf()
}
RxJava has more specific flatMapMaybe, but Reactor doesn't seem to have an equivalent.
I often stumble upon this problem but don't see a common implementation: how do I idiomatically (functionally) find an element, stop search after the match, and also return a different type (i.e. map whatever matched to another type)?
I've been able to do a workaround with
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? =
fold(AtomicReference<T>()) { ref, from ->
if (ref.get() != null) return#fold ref
ref.set(block(from))
ref
}.get()
fun main() {
Files.list(someDir).asSequence().map { it.toFile() }.mapFirst { file ->
file.useLines { lines ->
lines.mapFirst { line ->
if (line == "123") line.toInt() else null
}
}
}?.let { num ->
println("num is $num") // will print 123 as an Int
} ?: println("not a single file had a line eq to '123'")
}
But that doesn't stop on the match (when block() returns non-null) and goes to consume all files and all their lines.
A simple for loop is enough to implement mapFirst:
fun <F,T> Sequence<F>.mapFirst(block: (F) -> T?): T? {
for (e in this) {
block(e)?.let { return it }
}
return null
}
If you need a solution without introducing your own extensions (though there's nothing wrong with it), you can use mapNotNull + firstOrNull combination:
files.asSequence()
.mapNotNull { /* read the first line and return not null if it's ok */ }
.firstOrNull()
I would not map the values you discard then, instead do it like this:
sequenceOf(1, 2, 3)
.firstOrNull() { it == 2 }
?.let { it * 2 } ?: 6
First you find the value that matches your condition, then you transform it too whatever you want. In case you don't find a matching element, you assign a default value (in this case 6).
I have a List<T?> containing null values (which, I suppose, is not forbidden). If one element of this list is null, I want the entire list to be null (what Haskell people call sequence). The following pseudocode demonstrates what I want to do:
fun <T> sequence(a : List<T?>) : List<T>? {
return
a.fold(
listOf(),
{
prevArray, element ->
if(element == null) null else prevArray + element
})
}
This is pseudocode because the compiler complains that Null can not be a value of a non-null type kotlin.collections.List<T>.
What's the idiomatic way to express what I want to in Kotlin? Using Java's Optional type, this is at least compilable:
fun <T> sequence(a : List<T?>) : Optional<List<T>> {
return
a.fold(
Optional.of(listOf()),
{
prevArray, element ->
if(element == null) Optional.empty<List<T>>() else Optional.of(prevArray + element)
})
}
But Kotlin has many operators and functionalities regarding null handling, so I thought using null directly would be more idiomatic.
You can use a non-local return to return from the sequence function:
fun <T> sequence(a: List<T?>): List<T>? {
return a.fold(listOf()) {
prevArray, element ->
if (element == null) return null else prevArray + element
}
}
However, I would solve the problem you described with a simple if-expression, to prevent lots of list allocations which happen because the list addition creates a new list backed by array for each element. The unchecked cast warning is suppressed below because the compiler cannot figure out that the list does not contain nulls at that point, although we can clearly see that is the case:
fun <T> sequence(a: List<T?>): List<T>? {
#Suppress("UNCHECKED_CAST")
return if (a.any { it == null }) null else a as List<T>
}
Using Konad library you can now do the following:
fun <T> sequence(a : List<T?>) : List<T>? = a.flatten()?.toList()