我想根据对象中的两个字段对对象列表进行分组。
假设我有一个这样的对象:
data class ItemDataDTO(
val itemId: BigInteger?,
val sequence: BigInteger?,
val serialNumber: String?,
val pickingId: String?,
val runId: String? = null,
val warehouse: String?,
)
现在我有一个包含很多项目的
ItemDataDTO
列表。我想按 runId
和 pickingId
将它们分组(因为我需要那些具有相同 pickingId
和 runId
的项目以某种方式分组。)
val items: List<ItemDataDTO> = someItemRepository.getItemsForWarehouse("someWarehouseId")
val groupedItems = items.groupBy({ it.runId }, { it.pickingId })
这行不通。我发现我可以将
groupingBy()
函数与 Triple
一起使用,但我希望它们按两个值分组...
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, null) }
但这也不太管用。我尝试使用
null
:添加第三个参数而不是
it.warehouse
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, it.warehouse) }
它返回一个
Grouping<ItemDataDTO, Triple<String?, String?, String?>>
的实例,我不知道如何处理这个对象。
我该怎么做才能正确地对这些对象进行分组?
在完美的世界中,我想将此列表转换为类似以下内容的列表:
data class PickingList(
val runId: String,
val pickingId: String,
val items: List<ItemDataDTO>,
)
所以输出将是
List<PickingList>
。
其实没什么特别的!
groupBy
采用 keySelector
函数,该函数返回 某个值 用作该项目的键。因此,如果您想匹配两个属性,则该关键项需要由这两个值组成。
A
Triple
包含两个项目是 Pair
,所以你可以这样做:
// just FYI, "it.runId to it.pickingId" is shorthand for a Pair - you see it more
// when defining key/value pairs for maps though. "Pair(x, y)" might read better here
// since you're really just combining values, not describing how one relates to the other
items.groupBy { Pair(it.runId, it.pickingId) }
因此每个项目都会产生一个具有这两个值的
Pair
。具有匹配的 Pair
的任何其他项目(就 equals
功能而言)将被放入同一组中。这有点像添加到 Map
,只不过如果键已经存在,则该值将添加到列表中而不是覆盖以前的值。
您确实可以使用任何键来做到这一点。
Pair
和 Triple
只是快速、通用的便利类,用于将一些项目捆绑在一起 - 但很多时候最好定义自己的数据结构,例如使用 data class
。只要具有相同数据的两个实例相等,它们就被视为相同的分组键。
至于你想要的输出,使用
PickingList
...你可以使用类似的东西进行分组操作 - 但在这种情况下,你必须自己重新实现groupBy
。您必须选择一个项目,并根据您想要考虑的属性计算出其组合键。然后,您需要在为组创建的某个商店中找到该密钥的匹配项如果它是
PickingList
的列表,您需要遍历每个列表,将其 ID 与您想要的 ID 进行比较,如果找到匹配则添加到其列表中,如果找不到则创建对象.如果您要存储
Pair(id1, id2) -> PickingList
的映射,那么就生成查找键而言,这与
groupBy
的工作原理很接近。在这种情况下,您可能只想使用
groupBy
对所有项目进行分组,然后转换最终的地图:
items.groupBy { Pair(it.runId, it.pickingId) }
.map { (ids, list) ->
PairingList(runId = ids.first, pickingId = ids.second, items = list)
}
这会获取每个映射条目(ID 的 Pair
以及按这些 ID 分组的所有事物的列表),并使用它从该键/值数据创建
PairingList
。基本上,一旦您对所有数据进行了分组,您就可以将其转换为您想要使用的数据结构。
Pair
更好 -
it.first
并没有真正告诉您
Pair
中的值 is是什么,只是它是第一个两个值的。而
data class IdCombo(val runId: String, val pickingId: String)
与 Pair
的工作方式相同,但属性具有有用的名称,并使您的代码更具可读性且不易出现错误:
map { (ids, list) ->
// didn't even bother with the named arguments, since the names are in
// the ids object now!
PairingList(ids.runId, ids.pickingId, items = list)
}
data class Employee(val name: String, val age: Int, val payGrade: String)
fun main() {
val people = listOf(
Employee("Mark", 42, "A"),
Employee("Helen", 38, "C"),
Employee("Tracy", 32, "B"),
Employee("Katherin", 42, "A"),
Employee("John", 32, "B")
)
val items = people.groupBy { Pair(it.payGrade, it.age) }
val keys = items.keys.distinct()
items.forEach {
details ->
val key = details.key
val values = details.value
println("$key:values = $values")
values.forEach {
emp ->
var pg = emp.payGrade
var age = emp.payGrade
var name = emp.name
println("payGr $pg, age $age, name $name")
}
}