towi
towi

Reputation: 22267

How best to find an element in nested lists?

Kotlin provides some usingful extension functions allow stream-like programming.

For example, if I look for an element in a list I can use find:

return list.find { n -> n>4 && n<6 }

But when I have a have nested lists this seems not practical for me. I have tu use forEach then -- luckyly I can return from an inner Lambda with Kotlin:

private fun findUsingForEach(data: List<List<Int>>, pred : (Int) -> Boolean) : Optional<Int> {
    data.forEach { list ->
        list.forEach { n ->
            if( pred(n) ) return Optional.of(n)
        }
    }
    return Optional.empty()
}

It seems fo me that forEach is not the right tool for that. Is there a more functional way to du this? filter comes to mind, but the nesting causes problems.

That follwing is the test I use for the function abouve:

@Test
open fun findTest() {
    val data = listOf( listOf(1,2,3), listOf(3,4,5,6), listOf(), listOf(6,7,8) )
    val e = findUsingForEach( data, { n -> n>4 && n < 6 } )
    assertEquals(5, e.get())
}

Upvotes: 1

Views: 2904

Answers (2)

ice1000
ice1000

Reputation: 6569

If you just want to reduce codes and you don't care much about efficiency, try this.

list.flatten().find { your pred here }

Or

list.flatMap { it }.find { your pred }

Or create a useful utility which doesn't create new lists (faster/lower memory taken):

inline fun <T> Iterable<Iterable<T>>.forEachEach(f: (T) -> Unit) =
    forEach { it.forEach(f) }

Upvotes: 3

Salem
Salem

Reputation: 14897

You could flatten the list:

fun <T> Iterable<Iterable<T>>.flatten(): List<T> (source)

Returns a single list of all elements from all collections in the given collection.

val data = listOf(listOf(1, 2, 3), listOf(3, 4, 5, 6), listOf(), listOf(6, 7, 8))
data.flatten().find { n -> n > 4 && n < 6 }

This will return a single list with the elements of the sublists in order. Then you can use find as usual.

In your example,

{{1, 2, 3}, {3, 4, 5, 6}, {}, {6, 7, 8}}

becomes

{1, 2, 3, 3, 4, 5, 6, 6, 7, 8}

and the result of find on this list is 5.

However, this will create a new list. Take a look at the source of flatten:

/**
 * Returns a single list of all elements from all collections in the given collection.
 */
public fun <T> Iterable<Iterable<T>>.flatten(): List<T> {
    val result = ArrayList<T>()
    for (element in this) {
        result.addAll(element)
    }
    return result
}

If you want to save memory, create a Sequence from your list first:

data.asSequence()

and then perform your operations on this sequence:

data.asSequence().flatten().find { n -> n > 4 && n < 6 }

Side note: your predicate, n > 4 && n < 6, is simply equivalent to n == 5.

Upvotes: 4

Related Questions