scala:两个映射的并集,其键类型相同,其值类型是元素的集合,但其类型不同

时间:2017-09-14 07:43:10

标签: scala collections idiomatic

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

考虑以下设想的例子:

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集合库中没有任何单一的函数可以做到这一点。

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

我在这里发布,因为我无法轻易找到一个看似标准操作的在线解决方案。

3 个答案:

答案 0 :(得分:4)

这似乎有效。

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

答案 1 :(得分:4)

只是为了好奇,这里是如何使用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),它再次使用|+|来合并AB。在此,A = List[Child]B = List[Pet]
    • 用于任何类型的列表(以及字符串,向量或流)它进行连接。这就是为什么我必须将Map值的类型更改为List s - 对于泛型Seq,此操作未定义

结果:

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

答案 2 :(得分:0)

要回答我自己的问题,以下是我解决它的方式,但它似乎过于漫长而复杂:

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