高效的Scala惯用方法可以选择85%的排序值?

时间:2015-03-30 07:04:43

标签: scala

鉴于数字列表没有增加,我想在列表中选择前85%的值。这是我目前正在做的事情。

scala> val a = Array(8.60, 6.85, 4.91, 3.45, 2.74, 2.06, 1.53, 0.35, 0.28, 0.12)
a: Array[Double] = Array(8.6, 6.85, 4.91, 3.45, 2.74, 2.06, 1.53, 0.35, 0.28, 0.12)

scala> val threshold = a.sum * 0.85
threshold: Double = 26.2565

scala> val successiveSums = a.tail.foldLeft(Array[Double](a.head)){ case (x,y) => x ++ Array(y + x.last) }
successiveSums: Array[Double] = Array(8.6, 15.45, 20.36, 23.81, 26.549999999999997, 28.609999999999996, 30.139999999999997, 30.49, 30.77, 30.89)

scala> successiveSums.takeWhile( x => x <= threshold )
res40: Array[Double] = Array(8.6, 15.45, 20.36, 23.81)

scala> val size = successiveSums.takeWhile( x => x <= threshold ).size
size: Int = 4

scala> a.take(size)
res41: Array[Double] = Array(8.6, 6.85, 4.91, 3.45)

我想改进它的

  • 性能
  • 代码大小

有什么建议吗?

4 个答案:

答案 0 :(得分:4)

在代码大小上,请考虑此oneliner,

a.take( a.scanLeft(0.0)(_+_).takeWhile( _ <= a.sum * 0.85 ).size - 1 )

此处scanLeft累积了添加内容。

在性能方面,标记中间值可能有助于不重新计算相同的操作,即

val threshold = a.sum * 0.85
val size = a.scanLeft(0.0)(_+_).takeWhile( _ <= threshold ).size - 1
a.take( size )

答案 1 :(得分:2)

榆树的答案有一些改进的空间:
1)您不需要计算2次总和 2)您可以避免使用takeWhile方法创建其他集合,而是使用indexWhere

val sums = a.scanLeft(0.0)(_ + _)
a.take(sums.indexWhere(_ > sums.last * 0.85) - 1)

答案 2 :(得分:1)

没有库方法可以完全按照您的意愿行事。通常,如果你想要一些表现良好的东西,你可以使用尾递归方法来找到总和,并找到总和的第85百分位数的点。像

这样的东西
def threshold(
  xs: Array[Double], thresh: Double,
  i: Int = 0, sum: Double = 0
) {
  val next = sum + x(i)
  if (next > thresh) xs.take(i)
  else threshold(xs, thresh, i+1, next)
}

答案 3 :(得分:0)

在这种情况下,我会稍微使用可变状态。请参阅以下代码:

val a = Array(8.60, 6.85, 4.91, 3.45, 2.74, 2.06, 1.53, 0.35, 0.28, 0.12)

def f(a: Array[Double]) = {
  val toGet = a.sum * 0.85
  var sum = 0.0
  a.takeWhile(x => {sum += x; sum <= toGet })
}

println(f(a).deep) //Array(8.6, 6.85, 4.91, 3.45)

在我看来,它是可以接受的,因为功能f没有任何副作用