使用 RxJava 生成一个映射,其中键是 Kotlin 枚举的值,映射的值来自另一个 RxJava 流

Using RxJava to generate a map where keys are values of a Kotlin enum and map's values come from another RxJava stream

简介

假设我有一个 Kotlin 枚举 class:

enum class Type {
    ONE,
    TWO,
    THREE,
    FOUR
}

和以下数据class:

data class Item(
    val name: String,
    val type: Type
)

然后我有一个 Single 发出一个 Item 的列表 – 可以通过任何方式但为了举例目的,假设它看起来像这样:

val itemsSingle = Single.just(listOf(
    Item("A", Type.ONE),
    Item("B", Type.ONE),
    Item("C", Type.TWO),
    Item("D", Type.THREE),
))

问题

我想要实现的是拥有一个 RxJava 流,它将输出一个映射,其中键来自 Type,值是 Item 列表匹配给定的 Type 值(其中 Item 的未确定、未排序的列表由其他 Single 流提供)。签名将是:

Single<Map<Type, List<Item>> // or Observable<Map<Type, List<Item>>

一个额外的要求是地图的键应该总是从 Type 枚举中耗尽所有值,即使 itemsSingle 流不包含某些 Type 值的项目(或没有项目在全部)。因此,对于提供的 itemsSingle 示例流,返回的映射应如下所示:

{
    ONE:   [ Item(name: "A", type: ONE), Item(name: "B", type: ONE) ],
    TWO:   [ Item(name: "C", type: TWO) ],
    THREE: [ Item(name: "D", type: THREE) ],
    FOUR:  []
}

尝试

综上所述,我通过以下步骤取得了预期的结果:

  1. 为了满足穷尽所有 Type 枚举值的要求,我首先创建一个映射,其中包含所有可能的 Type 值的空列表:
val typeValuesMap = Type.values().associate { it to emptyList<Item>() }
val typeValuesMapSingle = Single.just(typeValuesMap)

// result: {ONE=[], TWO=[], THREE=[], FOUR=[]}
  1. 我可以得到一张地图,其中包含来自 itemsSingle 的项目,这些项目分组在相应的 Type 值键下:
val groupedItemsMapSingle = itemsSingle.flattenAsObservable { it }
    .groupBy { it.type }
    .flatMapSingle { it.toList() }
    .toMap { list -> list[0].type } // the list is guaranteed to have at least one item

// result: {ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}
  1. 最后,我可以使用 combineLatest 运算符合并两个列表,并覆盖给定 Type 值的初始空项目列表 if itemsSingle 包含任何 Items 对于此 Type 值:
Observable.combineLatest(
    typeValuesMapSingle.flattenAsObservable { it.entries },
    groupedItemsMapSingle.flattenAsObservable { it.entries }
) { a, b -> listOf(a, b) }
    .defaultIfEmpty(typeValuesMap.entries.toList()) // in case itemsSingle is empty
    .flatMapIterable { it }
    .collect({mutableMapOf<Type, List<Item>>()}, { a, b -> a[b.key] = b.value})

// result: {FOUR=[], ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}

总结

如您所见,看似简单的操作代码却不少。所以我的问题是 – 是否有更简单的方法来实现我想要的结果?

只需将空列表映射与填充列表映射合并

val result = itemsSingle.map { items->
   Type.values().associateWith { listOf<Item>() } + items.groupBy { it.type }
}