在 Kotlin 中从多个 List<Any> 对象构造一个 List<Map> 对象

Construct a List<Map> object out of multiple List<Any> objects in Kotlin

我在 kotlin 中有多个不同的数据 类,它们保存在长度完全相同的额外列表中。现在我想将它们组合成一个结果列表,其中包含它们作为具有相同长度(1000 个条目)的地图。我尝试使用 for 循环和预填充的地图列表,但对我来说这看起来有点乱,所以我问自己是否有更简洁的方法来执行此操作,尤其是使用 kotlin。

data class One(
    val a: Double,
    val b: Double,
    var c: Double
)
data class Two(
    val d: Double,
    val e: Double,
)

fun main() {
    val oneList: List<One> = listOf(One(1.0, 0.0, 2.0), One(3.0, 5.0, 10.0))
    val twoList: List<Two> = listOf(Two(5.0, 2.0), Two(7.0, 1.0))
    val results: List<MutableMap<String,Double>> = (0..1).map{ mutableMapOf() }
    for(i in 0 .. 1){
        results[i]["a"] = oneList[i].a
        results[i]["b"] = oneList[i].b
        results[i]["c"] = oneList[i].c
        results[i]["d"] = twoList[i].d
        results[i]["e"] = twoList[i].e
    }
}

问题是,我有大约 10 个 类,每个对象中有大约 2-3 个成员,因此代码大约有 30 多行...结果应该如下所示:

[
    {
        a: 1.0,
        b: 0.0,
        c: 2.0,
        d: 5.0,
        e: 2.0
    },
    {
        a: 3.0,
        b: 5.0,
        c: 10.0,
        d: 7.0,
        e: 1.0
    }
]

您可以使用 .zip in order to link the 2 lists and then directly create the list using .map and a simple mapOf() 创建地图。

val results = oneList.zip(twoList).map { (one, two) ->
    mapOf("a" to one.a, "b" to one.b, "c" to one.c, "d" to two.d, "e" to two.e)
}

至于实际为很多 classes 执行此操作......不是 100% 确定。您 could 可能会使用反射,但这通常不是您在实际程序中使用的东西。另一种方法是在每个 classes 中创建一个函数来提供地图,然后将这些地图从上面的 .map 中添加到一起。这样,代码看起来会更干净一些。大致如下:

data class One(
    val a: Double,
    val b: Double,
    var c: Double
){
    fun toMap(): Map<String, Double> {
        return mapOf("a" to a, "b" to b, "c" to c)
    }
}

//.....

val results = oneList.zip(twoList).map { (one, two) -> one.toMap() + two.toMap() }

但您很快就会注意到 zip 不适用于 2 个以上的列表。我建议实施这样的事情:Zip multiple lists SO。但这也行不通,因为您的 classes 属于不同类型。我要做的是创建一个具有 toMap() 乐趣的抽象 class,然后您需要的所有 class 都可以继承它。它看起来像这样:

abstract class MotherClass(){
    abstract fun toMap(): Map<String, Double>
}

data class One(
    val a: Double,
    val b: Double,
    var c: Double
):MotherClass() {
    override fun toMap(): Map<String, Double> {
        return mapOf("a" to a, "b" to b, "c" to c)
    }
}

// ...

val results = zip(oneList, twoList /*, threeList, etc */).map { list -> list.map { it.toMap() } }

所以在一天结束时,您想要一个 属性 名称的 String 表示,映射到它的值?我想你有两个选择:

  • 使用反射获取所有成员名称,过滤您想要的类型(例如,仅 Doubles),因此您的 Strings 直接来自 class在运行时
  • 在某处定义一个String作为每个属性的标签,并尽可能将其与class联系起来,这样就很容易维护。因为它不是从 属性 本身派生的,所以你必须保持 属性 名称和它的标签同步——两者之间没有内在联系可以使它自动化

你已经在你的代码中做了后者 - 你在构建你的 results 地图时使用了硬编码的任意标签,如 "a""b",这就是你'重新连接带有 属性 的标签(例如 results[i]["a"] = oneList[i].a)。所以这里有另一种方法可以解决这个问题!

Kotlin playground

interface Mappable {
    // allows us to declare classes as having the property map we're using
    abstract val propertyMap: Map<String, Double>
}

data class One(
    val a: Double,
    val b: Double,
    var c: Double
) : Mappable {
    // you have to do this association somewhere - may as well be in the class itself
    // you could also use reflection to build this map automatically
    override val propertyMap = mapOf("a" to a, "b" to b, "c" to c)
}
data class Two(
    val d: Double,
    val e: Double,
) : Mappable {
    override val propertyMap = mapOf("d" to d, "e" to e)
}

fun main() {
    val oneList = listOf(One(1.0, 0.0, 2.0), One(3.0, 5.0, 10.0))
    val twoList = listOf(Two(5.0, 2.0), Two(7.0, 1.0))
    combine(oneList, twoList).forEach(::println)
}

fun combine(vararg lists: List<Mappable>): List<Map<String, Double>> {
    // lists could be different lengths, so we go until one of them runs out
    val length = lists.minOf { it.size }
    
    return (0 until length).map { index ->
        // create a combined map for each index
        mutableMapOf<String, Double>().apply {
            // visit each list at this index, grabbing the property map from each object
            // and adding its contents to the combined map we're building
            lists.map { it.elementAt(index).propertyMap }.forEach(this::putAll)
        }
    }
}

>> {a=1.0, b=0.0, c=2.0, d=5.0, e=2.0}
{a=3.0, b=5.0, c=10.0, d=7.0, e=1.0}

真正的问题在于,您在 属性 和您为其明确定义的标签之间引入了人为的联系 - 您必须确保保持这种联系,如果不这样做,您就得到了错误。我认为这是不可避免的,除非你使用反射。

另外,如果有任何机会这个数据最初来自像 JSON 这样的任意东西(其中验证和标记 属性 的问题在链的前面)你可以看看Kotlin 的 map delegate 看看它是否更适合这种方法

import kotlin.reflect.full.declaredMemberProperties

data class One(val a: Double, val b: Double, var c: Double)
data class Two(val d: Double, val e: Double)

val oneList: List<One> = listOf(One(1.0, 0.0, 2.0), One(3.0, 5.0, 10.0))
val twoList: List<Two> = listOf(Two(5.0, 2.0), Two(7.0, 1.0))

fun compounded(vararg lists: List<Any>): List<Map<String, Double>> {
  return lists
    .mapIndexed { index, _ ->
      lists
        .flatMap {
          it[index]::class.declaredMemberProperties.map { prop ->
            mapOf(prop.name to prop.call(it[index]) as Double)
          }
        }
        .fold(mapOf()) { acc, map ->
          mutableMapOf<String, Double>().apply { putAll(acc + map) }
        }
    }
}

val result = compounded(oneList, twoList)