0

I would like to write a function in Kotlin which takes a String array and sums up the length of all items in the array. I thought about something like this:

fun sumItems(values: Array<String?>): Int {
    var sum: Int = 0
    values.forEach { if(it != null) sum += it.length }
    return sum
}

This works great but unfortunately I can not call this method for Array<String> because I get a type mismatch error then. I also can not create a function sumItems(values: Array<String>): Int because it has the same JVM signature. I could cast my argument to Array<String?> but this is unsafe.

So is there any better way to do this in Kotlin?

6
  • do you really need that function as is? isn't fun sumItems(values: Array<String>) : Int already enough? If not, then just use Array<out String?> as parameter type. Yoni Gibbs answer already shows this and also how that method can even be more simplified... Commented Nov 20, 2018 at 15:12
  • 1
    I would declare it as sumItems(values: Array<String>) and when I need to pass an Array<String?> I would filter it with filterNotNull. Commented Nov 20, 2018 at 15:15
  • I would take the same route as @m0skit0. That makes the code way more clearer and cleaner ;-) Commented Nov 20, 2018 at 15:17
  • However filterNotNull returns a list :( Commented Nov 20, 2018 at 15:18
  • The problem with calling filterNotNull is that you're creating a second collection in memory. And you're having to do the loop twice. In most cases this is probably negligible, but if working with a lot of data it's not the most efficient way. Commented Nov 20, 2018 at 15:19

2 Answers 2

2

Try this:

fun sumItems(values: Array<out String?>) = values.sumBy { it?.length ?: 0 }

You might want to make it an extension method instead:

fun Array<out String?>.sumItems() = sumBy { it?.length ?: 0 }

This will work for both Array<String> and Array<String?> because of the out modifier on the generic type. What this states is that the values parameter (or the receiver of the extension method) must be an array that produces nullable strings. Obviously Array<String?> produces nullable strings so that's valid to pass in. But Array<String> also produces nullable strings, because strings can always be cast to nullable strings. This is explained in more detail here.

Sign up to request clarification or add additional context in comments.

6 Comments

I think that's not what is being asked: "I can not call this method for Array<String> because I get a type mismatch error then".
You can call it for both Array<String> and Array<String?>. I'll update my answer with more details..
Oh you're correct, I'm not very used to use out/in modifiers, very nice answer!
while being the correct answer to solve the problem, I can only suggest to eliminate the null values as soon as possible so that the code is not filled with tons of null-safe calls ;-)
Why doesn't this work with lists as well? If I have fun foo(c: MutableCollection<out String>) I can call foo(mutableListOf<String>()) but not foo(mutableListOf<String?>()). Any notes to that as well?
|
2

Even though Yoni Gibbs answer is correct, I would rather take another route here, i.e. working with a non-nullable type, e.g.:

fun sumItems(values: Array<String>) = values.sumBy { it.length }

And as also m0skit0 mentioned in the comment: if you really have null values in your list, filter them out before making the sum, e.g.:

val arrayWithNulls = arrayOf("hello", null, "world")
arrayWithNulls.filterNotNull()
              .let(::sumItems)

Or better yet: just skip that method and do:

arrayWithNulls.filterNotNull()
              .sumBy { it.length } // or .sumBy(String::length)

Why introducing a new function, if applying existing ones already suffices?

Try to eliminate the null values from your array early. Otherwise your code just gets more complex (adding lots of null-safe things) and that makes the code just less readable. That way you could also skip that filterNotNull.

2 Comments

Just be aware of my comment on the question itself: calling filterNotNull creates a new list in memory. Most probably the overhead here is negligible, but if you're working with large data sets you might want to avoid this. You could call asSequence on the original array to get round this though.
I see no real reason to keep null values within an array... that performance impact is negligible and probably completely avoidable ;-) Even if we would work with large data sets (then I assume even more that there are no null values), I would rather filter it once and use non-nullable types everywhere else...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.