Найти и вернуть первое совпадение во вложенных списках в Kotlin? - PullRequest
0 голосов
/ 10 октября 2018

Рассмотрим следующие два класса:

class ObjectA(val objectBs: List<ObjectB>,
              val otherFields: Any)

class ObjectB(val key: String,
              val otherFields: Any)

Задача состоит в том, чтобы найти и вернуть первый ObjectB с определенным ключом в список ObjectA.

Просто достичь цели простодостаточно, но делать это красиво и эффективно кажется довольно сложно.Я не могу найти ничего подобного функции «firstIn» или «findIn», которая позволила бы мне возвращать другой тип, кроме ObjectA, при итерации по списку ObjectA.

У меня есть несколько подходов, один из которых выглядитдовольно хорошо, но очень неэффективно:

listOfA.mapNotNull { 
    it.objectBs.firstOrNull { 
        item -> item.key == wantedKey
   } 
}.firstOrNull()

Очевидная неэффективность этого кода заключается в том, что он не прекратит итерацию по listOfA, когда найдет совпадение (и может быть только одно совпадение, просто чтобы бытьЧисто).Подходы, использующие фильтр или поиск, имеют аналогичные проблемы, требующие избыточных итераций по крайней мере для одного списка ObjectB.

Есть ли в стандартной библиотеке kotlins что-то, что могло бы охватить такой вариант использования?

Ответы [ 5 ]

0 голосов
/ 11 февраля 2019

Путем преобразования всех вложенных элементов в плоский Sequence их можно итеративно выполнять итерациями, а ненужные итерации исключаются.Этот трюк делается путем объединения asSequence и flatMap:

listOfA.asSequence().flatMap { it.objectBs.asSequence() }.find { it.key == wantedKey }

Я написал и запустил следующий код, чтобы убедиться, что он работает должным образом:

class PrintSequenceDelegate<out T>(private val wrappedSequence: Sequence<T>) : Sequence<T> by wrappedSequence {
    override fun iterator(): Iterator<T> {
        val wrappedIterator = wrappedSequence.iterator()
        return object : Iterator<T> by wrappedIterator {
            override fun next(): T =
                wrappedIterator.next().also { println("Retrieving: $it") }
        }
    }
}

fun <T> Sequence<T>.toPrintDelegate() = PrintSequenceDelegate(this)

fun main() {
    val listOfLists = List(3) { i -> List(3) { j -> "$i$j" } }
    println("List of lists: $listOfLists")
    val found = listOfLists.asSequence().toPrintDelegate().flatMap { it.asSequence().toPrintDelegate() }.find { it == "11" }
    println(if (found != null) "Found: $found" else "Not found")
}

Вывод:

List of lists: [[00, 01, 02], [10, 11, 12], [20, 21, 22]]
Retrieving: [00, 01, 02]
Retrieving: 00
Retrieving: 01
Retrieving: 02
Retrieving: [10, 11, 12]
Retrieving: 10
Retrieving: 11
Found: 11

Таким образом, мы видим, что элементы (12) после элемента, найденного в содержащем вложенном списке, не повторяются, равно как и следующие вложенные списки ([20, 21, 22]).

0 голосов
/ 16 октября 2018

Ничего особенного, но он делает свою работу эффективно:

fun findBWithKey(listOfA: List<ObjectA>, wantedKey: String): ObjectB? {
  listOfA.forEach { 
        it.objectBs.forEach { item ->        
             if(item.key == wantedKey){
                 return item
             }
        } 
    }

    return null
}

Я также хотел бы использовать map и first, но эффективное выполнение данной задачи становится ненужным с помощью этих функций расширения.

0 голосов
/ 10 октября 2018

Простой flatMap делает свое дело:

listOfA.flatMap { it.objectBs }.first { it.key == wantedKey }

Это в основном даст вам промежуточный список со всеми их объединенными, так что вы можете легко запросить первый соответствующий.

0 голосов
/ 10 октября 2018

Если вы хотите элегантное решение, вы можете просто сделать flatMap следующим образом:

val result: ObjectB? = listOfA.flatMap { it.objectBs }.firstOrNull { it.key == "myKey" }

Если вы хотите эффективность, вы можете сделать что-то вроде этого:

val result: ObjectB? = objectAs.firstOrNull {
    it.objectBs.map(ObjectB::key).contains("myKey")
}?.objectBs?.firstOrNull { it.key == "myKey" }

Вы также можете обернуть их в Optional и поместить в функцию, чтобы у пользователей этой операции был чистый API:

fun List<ObjectA>.findFirstObjectB(key: String): Optional<ObjectB> {
    return Optional.ofNullable(firstOrNull {
        it.objectBs.map(ObjectB::key).contains(key)
    }?.objectBs?.firstOrNull { it.key == key })
}
0 голосов
/ 10 октября 2018

Я бы посмотрел на сопрограммы или последовательности , если производительность критична.

Вы можете немного оптимизировать свой код, используя firstOrNull и для listOfA:

listOfA.filterNotNull().firstOrNull { item ->
    item.objectBs.firstOrNull { it.key == wantedKey } != null
}

Я бы провел некоторое тестирование производительности, чтобы выяснить, не вызывает ли этот код какие-либо проблемы, прежде чем сделать его слишком сложным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...