Kotlin 在一次迭代中查找列表中与条件匹配的任意2个对象

Kotlin 在一次迭代中查找列表中与条件匹配的任意2个对象,kotlin,collections,Kotlin,Collections,我有一个自定义对象列表。我想检索满足2个不同条件的任何2个。 例如 假定 我有一个列表。我想得到两个年龄==21或年龄==31 我知道我可以做一个循环,一旦我找到了这两个标记,我就可以使用一些标记来打破它,但我想知道是否有更多的Kotlin惯用方法来代替: var p1: Person? var p2: Person? list.forEach{ if(it.age == 21) { p1 = it } else if(it.age =

我有一个自定义对象列表。我想检索满足2个不同条件的任何2个。
例如
假定

我有一个
列表
。我想得到两个
年龄==21
年龄==31

我知道我可以做一个循环,一旦我找到了这两个标记,我就可以使用一些标记来打破它,但我想知道是否有更多的Kotlin惯用方法来代替:

var p1: Person?  
var p2: Person?  
list.forEach{  
  if(it.age == 21) {  
      p1 = it  
       
  }
  else if(it.age == 31) {  
      p2 = it  
  }  
  if(p1 != null && p2 != null) break  
}
您可以使用关键字筛选任何集合

    val matches = list.filter { if (it.age == 21 || it.age == 31) }

matches
将是一个列表,其中所有
Person
对象的年龄都是21或31岁。

我认为在
list
中没有这样的方法,最接近的语句应该是使用
list.first{it.age==21 | | it.age==31}
,这将得到第一个匹配给定谓词的项,然后打破循环,可能是您可以编写自己的扩展来过滤第一个
n
数字

fun <T> Iterable<T>.firstN(n: Int, predicate: (T) -> Boolean): List<T> {
    val output = ArrayList<T>()
    var count = 0
    for (element in this){
        if(count == n) break
        if (predicate(element)){
            count++
            output.add(element)
        }
    }
    return output
}

我只能想到两种可能的解决方案:

  • 一种低效但简单的方法(2次部分迭代)
  • 这将迭代两次,但一旦找到结果就会停止迭代

  • 根据您自己的解决方案,只迭代一次
  • 将进行的
    p1==null
    检查不需要多次重新设计相同的值,从而得到第一个结果。这是更好的,即使我们不一定要寻找第一个结果,因为它不会作出多个任务


    Ps:这两种解决方案都会给您留下可为空的
    p1
    p2
    ,您必须记住在以后相应地处理它。

    不,您正在尝试执行相当具体的操作,因此我认为没有内置函数,您需要自己实现它。由于您只想迭代列表一次,我认为最好:

  • 用传统的列表迭代实现它
  • 将其封装在一个通用扩展函数中,供惯用Kotlin使用

  • 正如许多人提到的,
    find
    可能是解决此类问题的最佳方法,但我们必须使用它两次,一次用于
    21
    ,另一次用于
    31

    即使
    find
    将返回单个对象:
    Iterable.find(谓词:(T)->Boolean):T?
    这并不意味着我们不能在同一个迭代中找到另一个

    例如:

    var ageParam = -1
    
    var p1: Person? = null
    var p2: Person? = list.find { person ->
        p1?.let {
            //p1 - the first person found, now we will iterate until age == ageParam(21/31)
            person.age == ageParam
        } ?: run {
            //p1 - the first person hasn't found yet.
            if (person.age == 21) {
                p1 = person // saving person
                ageParam = 31 // setting next condition to test
            } else if (person.age == 31) {
                p1 = person
                ageParam = 21
            }
            false // it's false in order to keep iterate after P1 found
        }
    }
    

    您的解决方案接近最优。但它不需要首先满足条件的对象。对于
    listOf(Person(id=1,age=21)、Person(id=2,age=21)、Person(id=3,age=31))
    它将返回
    p1=Person(id=2,age=21)
    。 要解决此问题,您需要在条件表达式中添加与
    null
    的额外比较(实现此操作后,第二个
    if
    -语句可以合并到第一个语句的分支中,以避免重复检查)。此外,您不能在
    forEach
    内部使用
    break
    ——它应该用简单循环代替。 总而言之:

    var p1:个人?=无效的
    变量p2:人?=无效的
    对于(列表中的它){
    如果(p1==null&&it.age==21){
    p1=它
    如果(p2!=null)中断
    }else if(p2==null&&it.age==31){
    p2=它
    如果(p1!=null)中断
    }
    }
    
    要概括这一点,您可以创建扩展函数:

    funiterable.takeFirstTwo(谓词1:(T)->布尔,谓词2:(T)->布尔):对{
    变量p1:T?=null
    变量p2:T?=null
    因为(在这方面){
    if(p1==null&&predicate1(it)){
    p1=它
    如果(p2!=null)中断
    }else if(p2==null&&predicate2(it)){
    p2=它
    如果(p1!=null)中断
    }
    }
    将p1返回到p2
    }
    //用法:
    val is21={it:Person->it.age==21}
    val为31={it:Person->it.age==31}
    val(p1,p2)=list.takeFirstTwo(is21,is31)
    
    您找到的两个条目必须分别满足不同的条件,还是它们都满足相同的条件?@gidds:现在它们不能满足相同的条件。2个对象和2个不同的条件。找到的任何2个第一次满足标准最简单的方法是使用两个
    。查找{it.age==fizz}
    ,不需要自己进行迭代或其他任何事情。@Alex.t:是的,这将是2个迭代,对于大列表来说效率不高。我想知道,如果不使用Java风格的代码,使用一条记录是否可行。然后,他将如何选择其中的两条记录,并保证其中一条满足条件1,另一条满足条件2?因为这是实际的问题。您如何保证原始列表符合此标准?如何执行
    .find{}
    ?如果它在那里,你就会得到它(不需要一直迭代所有元素,因为
    find
    将在找到第一个元素时停止)@KenWolf:你说的保证是什么意思?我确实希望可能没有这样的对象,所以它将为空。是的,但如果它不存在,您就不必做任何其他事情。在执行
    .filter
    时,您不知道每个结果元素都满足了哪些条件(这可能会导致一个空列表、一个充满
    年龄==21
    的列表、一个充满
    年龄==31
    的列表或实际的混合)。因此,您仍然需要再次检查
    过滤器的结果
    ,以获得每个条件的单个记录(问题的要求)。否则,这充其量只是一个不完整的答案。问题是它可能返回
    n
    Person
    ,并且
    age==21
    ,这取决于列表中元素的顺序。这可能返回21岁的2岁或@Alex.T指出的31岁。是的,我想我们必须维护谓词和计数的列表,因此,可以针对每一个标准的限制,做它的手册将是很多better@Jim顺便说一句,问题本身的解决方案也做了同样的事情,因为count++可以在age==21时激活两次,然后它将从t中断
    with(list.firstN(2){ it.age == 21 || it.age == 31}){
         if(size == 2){
             val (p1, p2) = this
         }
    }
    
        val list = listOf(Person(21), Person(23), Person(31), Person(20))
        
        var p1: Person? = list.find{ it.age == 21 }
        var p2: Person? = list.find{ it.age == 31 }
    
        val list = listOf(Person(21), Person(23), Person(31), Person(20))
        
        var p1: Person? = null
        var p2: Person? = null
        
        //forEach is a method, not an actual loop, you can't break out of it, this is a bypass (you can also enclose it into a fun and return from there to act as a break)
        run loop@{
            list.forEach {
                when {
                    p1 == null && it.age == 21 -> p1 = it
                    p2 == null && it.age == 31 -> p2 = it
                }
                if (p1 != null && p2 != null) return@loop
            }
        }
    
    data class Person(val age: Int)
    
    fun <T> Iterable<T>.findFirstTwo(
        predicateA: (T) -> Boolean,
        predicateB: (T) -> Boolean
    ): Pair<T, T> {
        var first: T? = null
        var second: T? = null
        val iterator = iterator()
    
        var remainingPredicate: (T) -> Boolean = { false }
    
        while (first == null && iterator.hasNext()) {
            val item = iterator.next()
            when {
                predicateA(item) -> {
                    first = item
                    remainingPredicate = predicateB
                }
                predicateB(item) -> {
                    first = item
                    remainingPredicate = predicateA
                }
            }
        }
    
        while (second == null && iterator.hasNext()) {
            val item = iterator.next()
            if (remainingPredicate(item)) {
                second = item
            }
        }
    
        require(first != null && second != null) { "Input does not satisfy predicates" }
    
        return Pair(first, second)
    }
    
    fun main() {
        val people = (1..20).map { Person(it) }
    
        val (seven, twelve) = people.findFirstTwo({ it.age == 12 }, { it.age == 7 })
        val (one, nineteen) = people.findFirstTwo({ it.age == 1 }, { it.age == 19 })
    
        val error = try {
            people.findFirstTwo({ it.age == 100 }, { it.age == 3 })
        } catch (e: Exception) {
            e.message
        }
    
        println(one)
        println(seven)
        println(twelve)
        println(nineteen)
        print(error)
    
    }
    
    Person(age=1)
    Person(age=7)
    Person(age=12)
    Person(age=19)
    Input does not satisfy predicates
    
    var ageParam = -1
    
    var p1: Person? = null
    var p2: Person? = list.find { person ->
        p1?.let {
            //p1 - the first person found, now we will iterate until age == ageParam(21/31)
            person.age == ageParam
        } ?: run {
            //p1 - the first person hasn't found yet.
            if (person.age == 21) {
                p1 = person // saving person
                ageParam = 31 // setting next condition to test
            } else if (person.age == 31) {
                p1 = person
                ageParam = 21
            }
            false // it's false in order to keep iterate after P1 found
        }
    }