如何通过两个字段对列表中的对象进行分组?

问题描述 投票:0回答:2

我想根据对象中的两个字段对对象列表进行分组。

假设我有一个这样的对象:

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>

kotlin collections grouping
2个回答
4
投票

其实没什么特别的!

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) }
    

0
投票
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") } }
    
© www.soinside.com 2019 - 2024. All rights reserved.