scala:键类型相同且值类型是元素集合但类型不同的两个映射的并集
scala: union of two maps whose key type is the same and whose value type is a collection of elements, but whose types are different
我想创建两个键类型相同、值类型是元素集合但类型不同的映射的联合。
考虑以下人为的示例:
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 集合库中没有一个函数可以做到这一点。
建议的解决方案可以完全基于标准库,也可以提出外部解决方案。
我post这里是因为我无法轻易找到看似标准操作的在线解决方案。
为了回答我自己的问题,以下是我解决它的方法,但它似乎过于冗长和复杂:
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")))
)
这似乎有效。
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()
。
出于好奇,以下是如何使用 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)
,它再次使用 |+|
来组合 A
和 B
。这里,A = List[Child]
和 B = List[Pet]
- 对于任何类型的列表(以及字符串、向量或流),它都会进行连接。这就是为什么我必须将 Map 值的类型更改为
List
s - 对于通用 Seq
s 此操作未定义
结果:
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())
)
我想创建两个键类型相同、值类型是元素集合但类型不同的映射的联合。
考虑以下人为的示例:
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 集合库中没有一个函数可以做到这一点。
建议的解决方案可以完全基于标准库,也可以提出外部解决方案。
我post这里是因为我无法轻易找到看似标准操作的在线解决方案。
为了回答我自己的问题,以下是我解决它的方法,但它似乎过于冗长和复杂:
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")))
)
这似乎有效。
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()
。
出于好奇,以下是如何使用 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)
,它再次使用|+|
来组合A
和B
。这里,A = List[Child]
和B = List[Pet]
- 对于任何类型的列表(以及字符串、向量或流),它都会进行连接。这就是为什么我必须将 Map 值的类型更改为
List
s - 对于通用Seq
s 此操作未定义
- 对于
结果:
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())
)