Kotlin 在一次迭代中查找列表中与条件匹配的任意2个对象
我有一个自定义对象列表。我想检索满足2个不同条件的任何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 =
例如
假定 我有一个
列表
。我想得到两个年龄==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
}
我只能想到两种可能的解决方案:
p1==null
检查不需要多次重新设计相同的值,从而得到第一个结果。这是更好的,即使我们不一定要寻找第一个结果,因为它不会作出多个任务
Ps:这两种解决方案都会给您留下可为空的
p1
和p2
,您必须记住在以后相应地处理它。不,您正在尝试执行相当具体的操作,因此我认为没有内置函数,您需要自己实现它。由于您只想迭代列表一次,我认为最好:
正如许多人提到的,
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
}
}