【问题标题】:scala: union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are differentscala:两个映射的联合,其键类型相同,其值类型是元素的集合,但类型不同
【发布时间】:2018-02-23 02:16:17
【问题描述】:

我想创建两个映射的联合,它们的键类型相同,值类型是元素的集合,但类型不同。

考虑以下人为的例子:

case class Child(name: String)
val peopleToChildren: Map[String, Seq[Child]] = 
  Map("max" -> Seq(Child("a"), Child("b")), 
    "yaneeve" -> Seq(Child("y"), Child("d")))

case class Pet(name: String)
val peopleToPets: Map[String, Seq[Pet]] = 
  Map("max" -> Seq(Pet("fifi")), 
    "jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))

val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
  // people may have children
  // people may have pets
  // would like a map from people to a tuple with a potentially empty list of children and a
  //     potentially empty list of pets
  // ???
}

什么是简洁、惯用但仍然清晰易读的方法?

我在标准 scala 集合库中发现没有一个函数可以做到这一点。

提出的解决方案可以仅基于标准库,也可以提出外部解决方案。

我在这里发布它是因为我无法轻松找到看似标准操作的在线解决方案。

【问题讨论】:

    标签: scala collections idioms


    【解决方案1】:

    这似乎有效。

    val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
      (peopleToChildren.keySet ++ peopleToPets.keySet).map { k =>
        k -> (peopleToChildren.getOrElse(k, Seq())
             ,peopleToPets.getOrElse(k, Seq()))
      }.toMap
    }
    

    得到所有的钥匙。对于每个键,在每个馈线地图上执行 getOrElse()

    【讨论】:

    • 和@OlegPyzhcov 的解决方案一样酷,而且确实如此,我最终使用了这个版本,因为它没有外部部门并且简洁
    【解决方案2】:

    只是为了好奇,这里是如何使用 Scalaz 完成的:

    import scalaz._, Scalaz._
    
    case class Child(name: String)
    
    val peopleToChildren = Map(
      "max"     -> List(Child("a"), Child("b")), 
      "yaneeve" -> List(Child("y"), Child("d"))
    )
    
    case class Pet(name: String)
    
    val peopleToPets = Map(
      "max"  -> List(Pet("fifi")), 
      "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
    )
    
    val peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = 
      peopleToChildren.strengthR(nil[Pet]) |+| peopleToPets.strengthL(nil[Child])
    

    解释:

    • nil[Pet] 只是 List.empty[Pet] 的别名
    • strengthR 对于给定的 Functor 元组包含值,因此其参数位于右侧。这里相当于peopleToChildren.mapValues(v => (v, nil[Pet]))
    • strengthL 一样,但是元素会加到左边
    • |+| 是给定 Semigroup 的附加运算符。这里的一个是递归派生的:
      • 对于Map[K, V],它使用|+| 来组合V 类型的值,如果两个映射中都存在给定键。如果该值仅存在于其中一个中,则将按原样保留。在这里,V = (List[Child], List[Pet])
      • 对于元组(A, B),它再次使用|+| 组合As 和Bs。在这里,A = List[Child]B = List[Pet]
      • 对于任何类型的列表(以及字符串、向量或流),它都会进行连接。这就是为什么我必须将 Map 值的类型更改为 Lists - 对于通用 Seqs,此操作未定义

    结果:

    peopleToChildrenAndPets: Map[String, (List[Child], List[Pet])] = Map(
      "max" -> (List(Child("a"), Child("b")), List(Pet("fifi"))),
      "jill" -> (
        List(),
        List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit"))
      ),
      "yaneeve" -> (List(Child("y"), Child("d")), List())
    )
    

    【讨论】:

    • 真的很酷!感谢您的深入解释!我还想知道,如何用猫来做这件事似乎应该对这段代码进行简单的转换
    • @Yaneeve strengthLtupleLeftstrengthRtupleRightnil[...] 不存在,所以使用 List.empty[...]List[...]() 并且导入是 import cats._, implicits._。可能会在 1.0.0-MF 上被破坏,所以如果出现任何问题,请尝试 0.9.0。
    【解决方案3】:

    为了回答我自己的问题,以下是我解决它的方式,但它似乎过于冗长和复杂:

    Welcome to the Ammonite Repl 1.0.2
    (Scala 2.11.11 Java 1.8.0_91)
    If you like Ammonite, please support our development at www.patreon.com/lihaoyi
    @ case class Child(name: String)
    defined class Child
    
    @ val peopleToChildren: Map[String, Seq[Child]] =
        Map("max" -> Seq(Child("a"), Child("b")),
          "yaneeve" -> Seq(Child("y"), Child("d")))
    peopleToChildren: Map[String, Seq[Child]] = Map("max" -> List(Child("a"), Child("b")), "yaneeve" -> List(Child("y"), Child("d")))
    
    @
    
    @ case class Pet(name: String)
    defined class Pet
    
    @ val peopleToPets: Map[String, Seq[Pet]] =
        Map("max" -> Seq(Pet("fifi")),
          "jill" -> Seq(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
    peopleToPets: Map[String, Seq[Pet]] = Map("max" -> List(Pet("fifi")), "jill" -> List(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
    
    @
    
    @ val peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = {
        // people may have children
        // people may have pets
        // would like a map from people to a tuple with a potentially empty list of children and a
        //     potentially empty list of pets
    
        val paddedPeopleToChildren =  peopleToChildren.map{ case (person, children) => person -> (children, List.empty[Pet])}
        val paddedPeopleToPets = peopleToPets.map{ case (person, pets) => person ->(List.empty[Child], pets)}
        val notGoodEnough = paddedPeopleToPets ++ paddedPeopleToChildren // this is here to show that it does not work since it overwrites the value of a key - Map(max -> (List(Child(a), Child(b)),List()), jill -> (List(),List(Pet(bobo), Pet(jack), Pet(Roger rabbit))), yaneeve -> (List(Child(y), Child(d)),List()))
    
        val allSeq = paddedPeopleToPets.toSeq ++ paddedPeopleToChildren.toSeq
        val grouped = allSeq.groupBy(_._1).mapValues(_.map { case (_, tup) => tup })
        val solution = grouped.mapValues(_.unzip).mapValues {case (wrappedChildren, wrappedPets) => (wrappedChildren.flatten, wrappedPets.flatten)}
        solution
      }
    peopleToChildrenAndDogs: Map[String, (Seq[Child], Seq[Pet])] = Map(
      "yaneeve" -> (ArrayBuffer(Child("y"), Child("d")), ArrayBuffer()),
      "max" -> (ArrayBuffer(Child("a"), Child("b")), ArrayBuffer(Pet("fifi"))),
      "jill" -> (ArrayBuffer(), ArrayBuffer(Pet("bobo"), Pet("jack"), Pet("Roger rabbit")))
    )
    

    【讨论】:

      猜你喜欢
      • 2022-08-12
      • 2021-12-22
      • 1970-01-01
      • 1970-01-01
      • 2022-12-18
      • 1970-01-01
      • 2022-08-03
      • 2019-01-12
      • 2021-11-11
      相关资源
      最近更新 更多