Scala:将列表转换为地图
Scala : transform a list into a map
我有一只动物 class 定义为
case class Animal(name: String, properties: List[String])
给定一个动物列表,我想要一张来自 属性 的地图 -> 满足 属性
的动物列表
例如,如果我输入
List(
Animal("Dog",
List("has tail",
"can swim",
"can bark",
"can bite")),
Animal("Tuna",
List("can swim",
"has scales",
"is edible")),
Animal("Black Mamba",
List("has scales",
"is venomous",
"can bite"))
)
输出应该是
Map(
"has tail" -> List(Dog)
"can swim" -> List(Tuna,Dog)
"can bark" -> List(Dog)
"has scales" -> List(Tuna,Snake)
"is edible" -> List(Tuna)
"is venomous" -> List(Snake)
"can bite" -> List(Dog,Snake)
)
我对函数式编程还很陌生。我可以用命令式的方式做到这一点,但一直在努力想出一个实用的解决方案。欢迎任何指点! :)
您想要开始获取键值对列表。我们可以通过首先了解如何将单个 Animal
转换为键值对列表来开始这个问题。您可能听说过 map
函数。这允许您通过对列表中的每个元素应用一个函数来转换列表和其他基本结构。我们可以在这里使用它来达到很好的效果:
animal.properties.map(property => (property, animal.name))
这里我们取一只动物的 properties
,并为每个输出应用匿名函数:property => (property, animal.name)
。此函数创建 属性 的元组(在本例中为键值对)以及动物的名称。
现在我们要将其应用于列表中的所有动物。这可能听起来像另一个 map
,但是我们会得到一个元组列表列表,而实际上我们只需要一个元组列表。那就是当您使用 flatMap
时,它接受一个方法,该方法 return 是一个列表并将其应用于每个元素,并展平列表。所以我们只是将上面的方法应用到每个元素上。
val kvps = animals.flatMap(animal => animal.properties.map(property => (property, animal.name))).toMap
现在我们有一个键值对列表。现在我们想按键对它们进行分组。 groupBy
方法将 return 元组列表,其中左侧是键,右侧是键值对列表。这几乎就是我们想要的,但我们只想要右侧的值。所以我们可以这样做:
kvps.groupBy { case (key, value) => key }.toMap.mapValues(keyValues => keyValues.map { case (key, value) => value })
总的来说可能是这样的:
animals.flatMap { animal =>
animal.properties map { property => (animal, property) }
}.groupBy { case (key, value) => key }.toMap mapValues { keyValues =>
keyValues map { case (key, value) => value }
}
当然,Scala 有大量的语法糖可以使这个方法非常简洁:
animals.flatMap(a => a.properties.map(_ -> a.name)).groupBy(_._1).toMap.mapValues(_.map(_._2))
case class Animal(name: String, properties: List[String])
val animals = List(
Animal("Dog", List("has tail","can swim","can bark","can bite")),
Animal("Tuna", List("can swim", "has scales", "is edible")),
Animal("Black Mamba", List("has scales", "is venomous", "can bite"))
)
animals
.flatMap(a => a.properties.map(ab => ab -> a.name))
.groupBy(_._1)
.map(g => g._1 -> g._2.map(_._2)).toMap
方法如下
- 创建元组(属性 -> animalNames)
- 分组依据属性
- 将元组 (属性, List((属性, name))) 映射到 (属性, List(name))
一种这样的方式如下
animals.
flatMap(a => a.properties.map(p => (p, a.name)))
.groupBy(_._1)
.mapValues(_.map(_._2))
flatMap
给出了 属性 -> 动物名称
的元组列表
groupBy
然后按 属性 名称分组,产生一个 Map[String, List[(String,String)]
,其中 Map
的键是 属性,值是一个属性 名称的元组 -> 动物名称
然后 mapValues
获取地图值的结果 List((String,String))
并将其转换为元组的第二部分,即动物的名称
在 Scala 2.13 中,您可以使用 groupMapReduces
Welcome to Scala 2.13.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_181).
Type in expressions for evaluation. Or try :help.
scala> :pa
// Entering paste mode (ctrl-D to finish)
case class Animal(name: String, properties: List[String])
val animals = List(
Animal("Dog", List("has tail","can swim","can bark","can bite")),
Animal("Tuna", List("can swim", "has scales", "is edible")),
Animal("Black Mamba", List("has scales", "is venomous", "can bite"))
)
(for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)).groupMapReduce(_.head)(x => List(x.last))(_ ++ _)
// Exiting paste mode, now interpreting.
class Animal
val animals: List[Animal] = List(Animal(Dog,List(has tail, can swim, can bark, can bite)), Animal(Tuna,List(can swim, has scales, is edible)), Animal(Black Mamba,List(has scales, is venomous, can bite)))
val res0: scala.collection.immutable.Map[String,List[String]] =
HashMap(
is venomous -> List(Black Mamba),
is edible -> List(Tuna),
can swim -> List(Dog, Tuna),
can bite -> List(Dog, Black Mamba),
can bark -> List(Dog),
has scales -> List(Tuna, Black Mamba),
has tail -> List(Dog))
转换代码是
(for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)).groupMapReduce(_.head)(x => List(x.last))(_ ++ _)
那我逐行解释一下
我们有输入数据,例如 key -> List(value)
的集合,我们想将其转换为 value -> List(key)
的集合
首先,我们需要将其展平为 (value, key)
对。这就是 for
表达式的作用。
for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)
它将return类似于 List(List(has tail, Dog), List(can swim, Dog), ...
然后我们应该将结果按 property
分组并将分组值减少到 List
,这就是 groupMapReduce
所做的。
- 第一部分,我们按
_.head
对结果进行分组,也就是说property
动物
- 第二部分,我们通过
x => List(x, last)
将分组值映射到动物的name
,这里x
是Seq(property, animal.name)
,它只包含两个元素,所以最后一个是 name
- 第三部分,我们联系所有
List
的动物名字。这样我们就得到了最终的结果。
我有一只动物 class 定义为
case class Animal(name: String, properties: List[String])
给定一个动物列表,我想要一张来自 属性 的地图 -> 满足 属性
的动物列表例如,如果我输入
List(
Animal("Dog",
List("has tail",
"can swim",
"can bark",
"can bite")),
Animal("Tuna",
List("can swim",
"has scales",
"is edible")),
Animal("Black Mamba",
List("has scales",
"is venomous",
"can bite"))
)
输出应该是
Map(
"has tail" -> List(Dog)
"can swim" -> List(Tuna,Dog)
"can bark" -> List(Dog)
"has scales" -> List(Tuna,Snake)
"is edible" -> List(Tuna)
"is venomous" -> List(Snake)
"can bite" -> List(Dog,Snake)
)
我对函数式编程还很陌生。我可以用命令式的方式做到这一点,但一直在努力想出一个实用的解决方案。欢迎任何指点! :)
您想要开始获取键值对列表。我们可以通过首先了解如何将单个 Animal
转换为键值对列表来开始这个问题。您可能听说过 map
函数。这允许您通过对列表中的每个元素应用一个函数来转换列表和其他基本结构。我们可以在这里使用它来达到很好的效果:
animal.properties.map(property => (property, animal.name))
这里我们取一只动物的 properties
,并为每个输出应用匿名函数:property => (property, animal.name)
。此函数创建 属性 的元组(在本例中为键值对)以及动物的名称。
现在我们要将其应用于列表中的所有动物。这可能听起来像另一个 map
,但是我们会得到一个元组列表列表,而实际上我们只需要一个元组列表。那就是当您使用 flatMap
时,它接受一个方法,该方法 return 是一个列表并将其应用于每个元素,并展平列表。所以我们只是将上面的方法应用到每个元素上。
val kvps = animals.flatMap(animal => animal.properties.map(property => (property, animal.name))).toMap
现在我们有一个键值对列表。现在我们想按键对它们进行分组。 groupBy
方法将 return 元组列表,其中左侧是键,右侧是键值对列表。这几乎就是我们想要的,但我们只想要右侧的值。所以我们可以这样做:
kvps.groupBy { case (key, value) => key }.toMap.mapValues(keyValues => keyValues.map { case (key, value) => value })
总的来说可能是这样的:
animals.flatMap { animal =>
animal.properties map { property => (animal, property) }
}.groupBy { case (key, value) => key }.toMap mapValues { keyValues =>
keyValues map { case (key, value) => value }
}
当然,Scala 有大量的语法糖可以使这个方法非常简洁:
animals.flatMap(a => a.properties.map(_ -> a.name)).groupBy(_._1).toMap.mapValues(_.map(_._2))
case class Animal(name: String, properties: List[String])
val animals = List(
Animal("Dog", List("has tail","can swim","can bark","can bite")),
Animal("Tuna", List("can swim", "has scales", "is edible")),
Animal("Black Mamba", List("has scales", "is venomous", "can bite"))
)
animals
.flatMap(a => a.properties.map(ab => ab -> a.name))
.groupBy(_._1)
.map(g => g._1 -> g._2.map(_._2)).toMap
方法如下
- 创建元组(属性 -> animalNames)
- 分组依据属性
- 将元组 (属性, List((属性, name))) 映射到 (属性, List(name))
一种这样的方式如下
animals.
flatMap(a => a.properties.map(p => (p, a.name)))
.groupBy(_._1)
.mapValues(_.map(_._2))
flatMap
给出了 属性 -> 动物名称
groupBy
然后按 属性 名称分组,产生一个 Map[String, List[(String,String)]
,其中 Map
的键是 属性,值是一个属性 名称的元组 -> 动物名称
然后 mapValues
获取地图值的结果 List((String,String))
并将其转换为元组的第二部分,即动物的名称
在 Scala 2.13 中,您可以使用 groupMapReduces
Welcome to Scala 2.13.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_181).
Type in expressions for evaluation. Or try :help.
scala> :pa
// Entering paste mode (ctrl-D to finish)
case class Animal(name: String, properties: List[String])
val animals = List(
Animal("Dog", List("has tail","can swim","can bark","can bite")),
Animal("Tuna", List("can swim", "has scales", "is edible")),
Animal("Black Mamba", List("has scales", "is venomous", "can bite"))
)
(for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)).groupMapReduce(_.head)(x => List(x.last))(_ ++ _)
// Exiting paste mode, now interpreting.
class Animal
val animals: List[Animal] = List(Animal(Dog,List(has tail, can swim, can bark, can bite)), Animal(Tuna,List(can swim, has scales, is edible)), Animal(Black Mamba,List(has scales, is venomous, can bite)))
val res0: scala.collection.immutable.Map[String,List[String]] =
HashMap(
is venomous -> List(Black Mamba),
is edible -> List(Tuna),
can swim -> List(Dog, Tuna),
can bite -> List(Dog, Black Mamba),
can bark -> List(Dog),
has scales -> List(Tuna, Black Mamba),
has tail -> List(Dog))
转换代码是
(for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)).groupMapReduce(_.head)(x => List(x.last))(_ ++ _)
那我逐行解释一下
我们有输入数据,例如 key -> List(value)
的集合,我们想将其转换为 value -> List(key)
首先,我们需要将其展平为 (value, key)
对。这就是 for
表达式的作用。
for {
animal <- animals
property <- animal.properties
} yield Seq(property, animal.name)
它将return类似于 List(List(has tail, Dog), List(can swim, Dog), ...
然后我们应该将结果按 property
分组并将分组值减少到 List
,这就是 groupMapReduce
所做的。
- 第一部分,我们按
_.head
对结果进行分组,也就是说property
动物 - 第二部分,我们通过
x => List(x, last)
将分组值映射到动物的name
,这里x
是Seq(property, animal.name)
,它只包含两个元素,所以最后一个是name
- 第三部分,我们联系所有
List
的动物名字。这样我们就得到了最终的结果。