【问题标题】:Scala : transform a list into a mapScala:将列表转换为地图
【发布时间】:2015-07-10 05:16:20
【问题描述】:

我有一个动物类定义为

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

我对函数式编程很陌生。我可以以命令的方式做到这一点,但一直在努力想出一个功能性的解决方案。任何指针都是最受欢迎的! :)

【问题讨论】:

  • ImmutableList 的 groupBy 函数你试过了吗?这看起来就是您要找的东西。
  • 这只是一个作业。
  • @Paul 不。这是 reddit 每日程序员的挑战 :))

标签: list scala dictionary functional-programming


【解决方案1】:

您希望获得一个键值对列表以开始。我们可以通过首先查看如何将单个 Animal 转换为键值对列表来开始这个问题。您可能听说过map 函数。这允许您通过将函数应用于列表中的每个元素来转换列表和其他基本结构。我们可以在这里使用它来达到很好的效果:

animal.properties.map(property => (property, animal.name))

这里我们使用动物的properties,并为每个输出应用匿名函数:property => (property, animal.name)。此函数创建属性的元组(在本例中为键值对)以及动物的名称。

现在我们要将其应用于列表中的所有动物。这可能听起来像另一个map,但我们会得到一个元组列表的列表,而实际上我们只需要一个元组列表。那是当您使用flatMap 时,它接受一个返回列表并将其应用于每个元素的方法,并展平列表。所以我们只是将上述方法应用于每个元素。

val kvps = animals.flatMap(animal => animal.properties.map(property => (property, animal.name))).toMap

现在我们有了一个键值对列表。现在我们要按它们的键对它们进行分组。 groupBy 方法将返回一个元组列表,其中左侧是键,右侧是键值对列表。这几乎是我们想要的,但我们只想要右侧的值。所以我们可以这样做:

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

【讨论】:

  • 接受您的回答,因为您详细解释了到达最后一个班轮的所有步骤。非常感谢!
【解决方案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

方法如下

  • 创建元组(属性 -> 动物名称)
  • 按属性分组
  • 将元组 (property, List((property, name))) 映射到 (property, List(name))

【讨论】:

  • 您能否为每行添加一个转换以提高可读性?谢谢。
【解决方案3】:

一种方法如下

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)) 并将其转换为元组的第二部分,即动物的名称

【讨论】:

    【解决方案4】:

    在 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 -&gt; List(value) 的集合,我们希望将其转换为 value -&gt; List(key) 的集合

    首先,我们需要将其展平为(value, key) 对。这就是for 表达式的作用。

    for {
      animal <- animals
      property <- animal.properties
    } yield Seq(property, animal.name)
    

    它会返回类似 List(List(has tail, Dog), List(can swim, Dog), ...

    那么我们应该将结果按property 分组,并将分组后的值减少为List,这就是groupMapReduce 所做的。

    • 在第一部分,我们将结果按_.head 分组,即动物的property
    • 第二部分,我们通过x =&gt; List(x, last)将分组值映射到动物的name,这里xSeq(property, animal.name),它只包含两个元素,所以最后一个是name
    • 在第三部分,我们联系所有动物名称的List。所以我们得到了最终的结果。

    【讨论】:

      猜你喜欢
      • 2020-07-06
      • 1970-01-01
      • 2021-09-14
      • 1970-01-01
      • 2011-11-01
      • 1970-01-01
      • 2022-01-12
      • 2020-02-09
      相关资源
      最近更新 更多