Scala 使用求和逻辑按属性从列表转换为映射

Posted

技术标签:

【中文标题】Scala 使用求和逻辑按属性从列表转换为映射【英文标题】:Scala convert from list to map by property with sum logic 【发布时间】:2021-09-21 08:50:59 【问题描述】:

我是 Scala 的新手,尝试使用一些求和逻辑从列表转换为映射,如下所示。

case class ProductProperty(name:String, value:Option[String]= None, options:List[OptionItem]=List())

case class OptionItem(title:Option[String], description:Option[String] = None , price:Int)

val properties = List(ProductProperty(name = "size", value = Some("val1") , options =  Some(List(OptionItem(price = 10) , OptionItem(price = 204))),
         ProductProperty(name = "size", value = Some("val2") , options = Some(List(OptionItem(price = 122) , OptionItem(price = 240))),
         ProductProperty(name = "color", value = Some("val3") , options = Some(List(OptionItem(price = 101) , OptionItem(price = 204))),
         ProductProperty(name = "color", value = Some("val13") , options = Some(List(OptionItem(price = 102) , OptionItem(price = 120))),
         ProductProperty(name = "Quantity", value = Some("ssval3") , options = Some(List(OptionItem(price = 1011) , OptionItem(price = 204))),
         ProductProperty(name = "Quantity", value = Some("ssval13") , options = Some(List(OptionItem(price = 1102) , OptionItem(price = 1210))
     )

我需要将其展平并根据其名称和计算价格创建新地图。

Map 
     "size" -> total price,
     "color" -> total price,
     "Quantity" -> total price

到目前为止我已经尝试过:

val optList =   properties.map( list =>
      list.map(op => op.options
        .flatMap(i => List(ProductProperty(name = op.name, value = Some(i.value)))))
        .flatten
    )

val totalPrice = price.add(optList.map(list =>
      list.map(_.options)
        .flatten.map(_.price.getOrElse(0)).sum)
      .getOrElse(0))
    
Map((optList.map(_.name).getOrElse(List())) -> totalPrice)

但这是不正确的。

【问题讨论】:

【参考方案1】:

第一个问题是更正示例数据以便编译。

case class OptionItem(title       :Option[String] = None
                     ,description :Option[String] = None
                     ,price       :Int)

case class ProductProperty(name    :String
                          ,value   :Option[String] = None
                          ,options :List[OptionItem] = List())

val properties =
  List(ProductProperty("size", Some("val1"), List(OptionItem(price = 10), OptionItem(price = 204)))
      ,ProductProperty("size", Some("val2"), List(OptionItem(price = 122), OptionItem(price = 240)))
      ,ProductProperty("color", Some("val3"), List(OptionItem(price = 101), OptionItem(price = 204)))
      ,ProductProperty("color", Some("val13"), List(OptionItem(price = 102), OptionItem(price = 120)))
      ,ProductProperty("Quantity", Some("ssval3"), List(OptionItem(price = 1011), OptionItem(price = 204)))
      ,ProductProperty("Quantity", Some("ssval13"), List(OptionItem(price = 1102), OptionItem(price = 1210))))

之后,应用groupBy()map()fold()reduce() 就很简单了。 Scala 2.13.x 同时提供了所有 3 个。

properties.groupMapReduce(_.name)(_.options.map(_.price).sum)(_+_)
//res0: Map[String,Int] = Map(size -> 576, color -> 527, Quantity -> 3527)

对于较旧的 Scala 版本,您必须将其分解为更小的步骤。

properties.groupBy(_.name)
          .mapcase (k,v) => k -> v.map(_.options.map(_.price).sum).sum

【讨论】:

我在 scala 版本 2.12 :) 谢谢,如果将来根本不需要使用 k (将从值构建键)。 properties.groupBy(.name) .mapValuescase (v) => v.toStirng -> v.map(.options.map(_.price).sum).sum mapValues() 在当前的 Scala 版本中已被弃用。它有一些奇怪的副作用。这就是我不使用它的原因。【参考方案2】:

补充 jwvh 答案,您可以使用 cats (也在2.12 稍微简化代码:

import cats.syntax.all._

val result = properties.foldMap 
  case ProductProperty(name, _, options) =>
    Map(name -> options.foldMap(_.price))

// result: Map[String,Int] = Map(size -> 576, color -> 527, Quantity -> 3527)

这里的技巧是foldMapmapfoldLeft 合并为一个步骤;所以options.foldMap(_.price)本质上和options.map(_.price).foldLeft(0)(_ + _)是一样的

但是,最有趣的部分是properties.foldMap,因为Monoid 用于Maps 基本上将两个映射合并在一起,当它们具有相同的键时,它只是将其值组合在一起。


可以看到运行here的代码

【讨论】:

以上是关于Scala 使用求和逻辑按属性从列表转换为映射的主要内容,如果未能解决你的问题,请参考以下文章

使用自定义逻辑过滤对象的 scala 集合

Scala - 将地图列表转换为地图

转换数据框列值并应用 SHA2 屏蔽逻辑

通过流将带有列表的列表对象转换为Java 8中的映射[重复]

Scala:将 Map 映射到元组列表

机器学习:逻辑回归