我正处于scala的第二个晚上,我正在抵制用scala写东西的冲动,我曾经在java中做过这些事情,并试图学习所有的习语。在这种情况下,我希望只使用闭包,映射和列表理解这样的东西来计算平均值。无论这是否是计算平均值的最佳方法,我只想知道如何在scala中执行这些操作仅用于学习目的
这是一个例子:下面的平均方法几乎没有实现。我有几个其他的方法来查找个人用户ID给出的评级使用TraversableLike的查找方法(我认为),但没有更多的是scala特定的,真的。我如何计算给定List [RatingEvent]的平均值,其中RatingEvent.rating是一个双值,我将以类似scala的方式计算该List的所有值的平均值?。
package com.brinksys.liftnex.model
class Movie(val id : Int, val ratingEvents : List[RatingEvent]) {
def getRatingByUser(userId : Int) : Int = {
return getRatingEventByUserId(userId).rating
}
def getRatingEventByUserId(userId : Int) : RatingEvent = {
var result = ratingEvents find {e => e.userId == userId }
return result.get
}
def average() : Double = {
/*
fill in the blanks where an average of all ratingEvent.rating values is expected
*/
return 3.8
}
}
经验丰富的scala pro如何填充该方法并使用scala的功能使其尽可能简洁?我知道如何在java中这样做,这是我想要避免的。
如果我在python中这样做,我认为最pythonic的方式是:
sum([re.rating. for re in ratingEvents]) / len(ratingEvents)
或者如果我强迫自己使用闭包(这是我至少想在scala中学习的东西):
reduce(lambda x, y : x + y, [re.rating for re in ratingEvents]) / len(ratingEvents)
这是我想在scala中学习的这些类型的东西。
你的建议?欢迎任何指向与此相关的优秀教程/参考资料:D
答案 0 :(得分:30)
如果您要对事物进行数学运算,使用List
并不总是最快的方式,因为List
不知道它有多长时间 - 所以ratingEvents.length
花费时间与长度成正比。 (不是很多很多时间,被授予,但它必须遍历整个列表才能告诉。)但是,如果你主要是操纵数据结构而只是偶尔需要计算总和或其他什么,那么它是不是代码的时间关键核心,那么使用List
就是花花公子。
无论如何,这样做的规范方法是使用折叠来计算总和:
(0.0 /: ratingEvents){_ + _.rating} / ratingEvents.length
// Equivalently, though more verbosely:
// ratingEvents.foldLeft(0.0)(_ + _.rating) / ratingEvents.length
或通过映射然后求和(仅限2.8):
ratingEvents.map(_.rating).sum / ratingEvents.length
有关地图和折叠的详细信息,请参阅this question on that topic。
答案 1 :(得分:10)
你可能一次性计算总和和长度,但我怀疑除了非常长列表之外,这有帮助。它看起来像这样:
val (s,l) = ratingEvents.foldLeft((0.0, 0))((t, r)=>(t._1 + r.rating, t._2 + 1))
val avg = s / l
我认为对于这个例子来说,Rex的解决方案要好得多,但在其他用例中,“折叠过度元组技巧”可能是必不可少的。
答案 2 :(得分:4)
由于mean
和其他描述性统计信息(如standard deviation
或median
在不同的上下文中是必需的,因此您还可以使用一个小的可重用隐式帮助程序类来实现更简化的链式命令:< / p>
implicit class ImplDoubleVecUtils(values: Seq[Double]) {
def mean = values.sum / values.length
}
val meanRating = ratingEvents.map(_.rating).mean
甚至可以seems to be以通用方式为所有数字类型编写它。
答案 3 :(得分:2)
尾递归解决方案既可以实现单次遍历,又可以避免高内存分配率
def tailrec(input: List[RatingEvent]): Double = {
@annotation.tailrec def go(next: List[RatingEvent], sum: Double, count: Int): Double = {
next match {
case Nil => sum / count
case h :: t => go(t, sum + h.rating, count + 1)
}
}
go(input, 0.0, 0)
}
以下是对百万元素列表中上述答案的方法的 jmh 测量:
[info] Benchmark Mode Score Units
[info] Mean.foldLeft avgt 0.007 s/op
[info] Mean.foldLeft:·gc.alloc.rate avgt 4217.549 MB/sec
[info] Mean.foldLeft:·gc.alloc.rate.norm avgt 32000064.281 B/op
...
[info] Mean.mapAndSum avgt 0.039 s/op
[info] Mean.mapAndSum:·gc.alloc.rate avgt 1690.077 MB/sec
[info] Mean.mapAndSum:·gc.alloc.rate.norm avgt 72000009.575 B/op
...
[info] Mean.tailrec avgt 0.004 s/op
[info] Mean.tailrec:·gc.alloc.rate avgt ≈ 10⁻⁴ MB/sec
[info] Mean.tailrec:·gc.alloc.rate.norm avgt 0.196 B/op
答案 4 :(得分:1)
我可以建议两种方式:
def average(x: Array[Double]): Double = x.foldLeft(0.0)(_ + _) / x.length
def average(x: Array[Double]): Double = x.sum / x.length
两者都很好,但在使用折叠的情况下,您不仅可以进行“+”操作,还可以将其替换为其他( - 或*)