【问题标题】:Finding the average of option values containg object in a list查找列表中包含对象的选项值的平均值
【发布时间】:2019-09-30 17:44:56
【问题描述】:

我是 Scala 新手,我一直在努力处理选项和列表。我有以下对象:

object Person {

    case class Person(fName: String,
                      lName: String,
                      jeans: Option[Jeans],
                      jacket: Option[Jacket],
                      location: List[Locations],
                      age: Int)

    case class Jeans(brand: String, price: Int, color: String)
...
}

我正在尝试编写函数,该函数以 person 类型的输入列表作为输入,并返回他们牛仔裤的平均价格:

def avgPriceJeans(input: List[Person]): Int

【问题讨论】:

  • val prices = persons.flatMap(_.jeans).map(_.price); prices.sum.toDouble / prices.length
  • 如果牛仔裤是 None,函数的行为是什么?在计算平均值的总和中应该考虑?

标签: scala scala-option


【解决方案1】:

当您有一个 列表 值并希望将它们全部减少为一个值时,应用某种操作。您需要fold,最常见的是foldLeft

正如您在scaladoc 中看到的那样。该方法接收一个初始值和一个组合函数。
很明显,初始值应该是zero。并且组合函数应该将当前累积并添加到当前牛仔裤的价格。

不过,现在我们有另一个问题,牛仔裤可能存在也可能不存在,因此我们使用选项。在这种情况下,我们需要一种方式来说明它们是否存在,给我他们的价格,如果不给我一个默认值(在这种情况下,另一个 zero 是有意义的)
这正是Option.fold 给我们的。

因此我们以这样的方式结束:

val sum = input.foldLeft(0) {
  (acc, person) => 
    acc + person.jeans.fold(ifEmpty = 0)(_.price)
}

现在您需要平均值,您只需将该总和除以计数即可。

但是,我们可以在同一个 foldLeft 中进行计数,以避免额外的迭代。
(我将返回类型以及 price 属性更改为 加倍以确保结果准确)

def avgPriceJeans(input: List[Person]): Double = {
  val (sum, count) = input.foldLeft((0.0d, 0)) {
    case ((accSum, accCount), person) =>
      (
        accSum   + person.jeans.fold(ifEmpty = 0.0d)(_.price),
        accCount + 1
      )
  }
  sum / count
}

【讨论】:

  • 0.0后面的d是什么意思?抱歉,我不知道 Scala 的语法。
  • 哦,我明白了,这意味着双重。
  • @AllenHan 是的,这只是为了确保编译器将 zero 视为 Double。一般来说,它不应该是必要的,但我有点喜欢它。 (PS:这是我所知道的所有语言的标准).
  • @pedrofurla 正如我所说,我假设价格也是 双倍。如果不是,那么绝对是。
  • @MarioGalic Lists 是简单的 ADT,要么是空的,要么是缺点。它不知道它的大小,它必须一直向下直到找到 Nil。有人可能会争辩说它可以将其大小存储为一个属性,但这会大大增加内存占用,因为 List 是由其他 Lists 构建的。而且由于在 FP 时尚中您通常不需要它的大小 (而且当您这样做时,您可以很容易地计算它) 那么它就是不值得花费。
【解决方案2】:

正如@SethTissue 指出的那样,这相对简单:

val prices = persons.flatMap(_.jeans.map(_.price))
prices.sum.toDouble / prices.length

第一行需要拆开:

从内到外,表达式jeans.map(_.price)jeans 的值,即Option[Jeans],并提取price 字段以给出Option[Int]flatMap 调用等效于 map 后跟 flattenmap 调用将此内部表达式应用于persons 的每个元素的jeans 字段。这会将List[Person] 变成List[Option[Int]]flatten 调用从Some[Int] 中提取所有Int 值并丢弃所有None 值。这为 List[Int] 提供了一个元素,每个 Person 有一个非空的 jeans 字段。

第二行简单地将List 中的值相加,将其转换为Double,然后除以列表的长度。添加错误检查留作练习!

【讨论】:

    【解决方案3】:

    一种方法是计算列表中每个元素的牛仔裤价格值。 之后,您可以对所有值求和(使用 sum 方法)并除以列表大小。 当牛仔裤为 None 且价格值为 0 时,我处理了这种情况(所以我认为它是总和)。

    代码如下:

    def avgPriceJeans(input: List[Person]): Int =
        input.map(_.jeans.map(_.price).getOrElse(0)).sum / input.size
    

    【讨论】:

      猜你喜欢
      • 2021-08-28
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-12
      • 1970-01-01
      相关资源
      最近更新 更多