ADev
ADev

Reputation: 5459

In Kotlin, how to check contains one or another value?

In Kotlin we can do:

val arr = intArrayOf(1,2,3)
if (2 in arr)
   println("in list")

But if I want to check if 2 or 3 are in arr, what is the most idiomatic way to do it other than:

if (2 in arr || 3 in arr)
   println("in list")

Upvotes: 54

Views: 89536

Answers (11)

we can use any() funtion and a Set

val arr = intArrayOf(1,2,3)

if(arr.any{ it in setOf(2,3) })
   println("in list")

Upvotes: 0

Adil Hussain
Adil Hussain

Reputation: 32103

For me, the best way to solve this is to define a containsAny(elements:) extension function on the Array and/or Collection class. All of the places where you need to check whether an Array or Collection contains any of the elements of another then remains nice and neat, as follows:

arrayOf(1, 2, 3).containsAny(2, 3)

The implementation detail is tucked away inside of that function and can be changed in a single place in future should you wish to change the implementation.

Here's an example implementation of an extension function defined on the Collection class, the details of which are inspired by other answers in this thread:

/**
 * Returns true if the receiving collection contains any of the specified elements.
 *
 * @param elements the elements to look for in the receiving collection.
 * @return true if any element in [elements] is found in the receiving collection.
 */
fun <T> Collection<T>.containsAny(vararg elements: T): Boolean {
    return containsAny(elements.toSet())
}

/**
 * Returns true if the receiving collection contains any of the elements in the specified collection.
 *
 * @param elements the elements to look for in the receiving collection.
 * @return true if any element in [elements] is found in the receiving collection.
 */
fun <T> Collection<T>.containsAny(elements: Collection<T>): Boolean {
    val set = if (elements is Set) elements else elements.toSet()
    return any(set::contains)
}

Likewise, here's an extension function defined on the Array class:

/**
 * Returns true if the receiving array contains any of the specified elements.
 *
 * @param elements the elements to look for in the receiving array.
 * @return true if any element in [elements] is found in the receiving array.
 */
fun <T> Array<T>.containsAny(vararg elements: T): Boolean {
    return any(elements.toSet()::contains)
}

Upvotes: 4

Francis
Francis

Reputation: 7104

You can make an extension function to check for more values:

infix fun <T> Iterable<T>.containsAny(values: Iterable<T>): Boolean =
    intersect(values).isNotEmpty()

Upvotes: 3

u8188526
u8188526

Reputation: 181

If you use Apache Commons in your project, you can use CollectionUtils.containsAny.

Upvotes: 0

Thiago
Thiago

Reputation: 13302

Have a look at this blog post: https://www.codevscolor.com/kotlin-check-array-contains-one-multiple-values

Sample code

fun main() {
    val givenArray = intArrayOf(1, 2, 3, 4, 5, 7, 9, 11)

    println(givenArray.any{ it == 5 || it == 12})
    println(givenArray.any{ it == 5 || it == 11})
    println(givenArray.any{ it == 15 || it == 21})
}

Upvotes: 1

Willi Mentzel
Willi Mentzel

Reputation: 29844

This is the shortest and most idiomatic way I can think of using any and in:

val values = setOf(2, 3)
val array = intArrayOf(1, 2, 3)

array.any { it in values }

Of course you can use a functional reference for the in operator as well:

array.any(values::contains)

I use setOf for the first collection because order does not matter.

Edit: I switched values and array, because of alex.dorokhow's answer. The order doesn't matter for the check to work but for performance.


The OP wanted the most idiomatic way of solving this. If you are after a more efficient way, go for aga's answer.

Upvotes: 58

Leo DroidCoder
Leo DroidCoder

Reputation: 15046

Another handy way is actually not Kotlin's but with use of Java Collections.
It is also good to know it.

Collections.disjoint(Collection<?> c1, Collection<?> c2)

Returns {@code true} if the two specified collections have no elements in common.

@Test
fun disjointCollections() {
    val list = listOf(1, 2, 3)

    assertTrue(Collections.disjoint(list, listOf(7, 8)))
    assertFalse(Collections.disjoint(list, listOf(1)))
}

Upvotes: 5

alex.dorokhov
alex.dorokhov

Reputation: 1228

Combining @aga and @willi-mentzel solutions for better efficiency and dynamic set of checked values:

val numbers = setOf(2, 3)
arrayOf(1, 2, 3).any(numbers::contains)

Is this case the array will be iterated completely only once (at most, in the worst case).

This is more efficient than (suggested by @WilliMentzel):

numbers.any(arrayOf(1, 2, 3)::contains) // don't do that!

Where the array will be iterated set.count times in the worst case.

Note that Set.contains has O(1) complexity, but IntArray::contains has O(N).

Of course, this optimization makes sense only if the set or array are big enough.

Upvotes: 12

Mateusz
Mateusz

Reputation: 691

You can use intersect method, it takes iterable as paramter and returns set containing only items that are both in your collection and in iterable you provided. Then on that set you just need to do size check.

Here is sample:

val array1 = arrayOf(1, 2, 3, 4, 5, 6)
val array2 = arrayOf(2, 5)

// true if array1 contains any of the items from array2
if(array1.intersect(array2.asIterable()).isNotEmpty()) {
    println("in list")
}

Upvotes: 25

s1m0nw1
s1m0nw1

Reputation: 81879

I think it's most readable to write the statement the other way around:

val arr = intArrayOf(1,2,3)
val match = setOf(2, 3).any(arr::contains)

It might be even possible to use ranges in certain scenarios:

val match = (2..3).any(arr::contains)

In the end, your solution looks pretty good to me already. Although not using fancy library functionality.

Upvotes: 2

aga
aga

Reputation: 29416

I'd use any() extension method:

arrayOf(1, 2, 3).any { it == 2 || it == 3 }

This way, you traverse the array only once and you don't create a set instance just to check whether it's empty or not (like in one of other answers to this question).

Upvotes: 63

Related Questions