fold/reduce 复杂累加器

fold/reduce with complex accumulator

我有一个如下所示的列表:

val myList = listOf(
    Message(
      id= 1,
      info = listOf(1, 2)
    ),
    Message(
      id= 1,
      info = listOf(3, 4)
    ),
    Message(
      id= 2,
      info = listOf(5, 6)
    ) 
)

如何转换它以便合并具有相同 id 的元素?

listOf(
    Message
      id= 1
      info = listOf(1, 2, 3, 4)
    ),
    Message
      id= 2
      info = listOf(5, 6)
    ) 
)

我试过以下方法,效果很好


myList
    .groupBy { it.id }   
    .map { entry ->
        val infos = entry.value.fold(listOf<Int>()) { acc, e -> acc + e.info }

        Message(
            id = entry.key,
            info = infos
        )
    }

但我想知道是否有 easier/cleaner/more 惯用的方法来合并这些对象。貌似单折就可以了,但是脑子转不过来

谢谢

您可以groupingBy the ids, then reduce,这将对每个组执行缩减。

myList.groupingBy { it.id }.reduce { id, acc, msg -> 
    Message(id, acc.info + msg.info) 
}.values

这当然会创建很多 MessageList 对象,但事实就是如此,因为它们都是不可变的。但也有可能这在宏伟的计划中并不重要。

如果你有这样的MutableMessage

data class MutableMessage(
    val id: Int,
    val info: MutableList<Int>
)

你可以这样做:

myList.groupingBy { it.id }.reduce { _, acc, msg ->
    acc.also { it.info.addAll(msg.info) }
}.values

不使用 reduce 或 fold 的解决方案:

data class Message(val id: Int, val info: List<Int>)

val list = listOf(
  Message(id = 1, info = listOf(1, 2)),
  Message(id = 1, info = listOf(3, 4)),
  Message(id = 2, info = listOf(5, 6))
)

val result = list
  .groupBy { message -> message.id }
  .map { (_, message) -> message.first().copy(info = message.map { it.info }.flatten() ) }

result.forEach(::println)

通过抽取出几个函数有自己的意义,可以大大提高可读性。

data class Message(val id: Int, val info: List<Int>) {
    fun merge(that: Message): Message = this.copy(info = this.info + that.info)
}

fun List<Message>.mergeAll() =
    this.reduce { first, second -> first.merge(second) }

fun main() {
    val myList = listOf(
        Message(
            id = 1,
            info = listOf(1, 2)
        ),
        Message(
            id = 1,
            info = listOf(3, 4)
        ),
        Message(
            id = 2,
            info = listOf(5, 6)
        )
    )

    val output = myList
        .groupBy { it.id }
        .values
        .map { it.mergeAll() }

    println(output)
}

也会选择 groupingBy but do it a bit differently via fold (compare also Grouping):

myList.groupingBy { it.id }
      .fold({ _, _ -> mutableListOf<Int>() }) { _, acc, el ->
        acc.also { it += el.info }
      }
      .map { (id, infos) -> Message(id, infos) }

这样你只有 1 个中间映射,每个键只有 1 个中间列表,它会累积你的值。最后,您将其转换为您需要的形式(例如转换为 Message)。也许你甚至不需要那个?也许地图已经是你想要的了?

在这种情况下,您可能希望使用如下内容(即缩小值的可变列表类型):

val groupedMessages : Map<Int, List<Int>> = myList.groupingBy { it.id }
    .fold({ _, _ -> mutableListOf() }) { _, acc, el ->
      acc.also { it += el.info }
    }