我在列表中有一堆项目,我需要分析内容以找出其中有多少是“完整”的。我从分区开始,但后来意识到我不需要两个列表,所以我切换到了折叠:
val counts = groupRows.foldLeft( (0,0) )( (pair, row) =>
if(row.time == 0) (pair._1+1,pair._2)
else (pair._1, pair._2+1)
)
但是我为很多并行用户提供了很多行,并且它导致了大量的GC活动(假设我...... GC 可以来自其他事情,但我怀疑这一点,因为据我所知,它会在折叠的每个项目上分配一个新的元组。
目前,我已将其重写为
var complete = 0
var incomplete = 0
list.foreach(row => if(row.time != 0) complete += 1 else incomplete += 1)
修复了GC,但引入了变种。
我想知道是否有一种方法可以在不使用变量而不滥用GC的情况下这样做?
修改
很难打电话给我收到的答案。大型列表上的var实现似乎要快得多(比如40%),甚至比尾部递归优化版本更实用,但应该是等效的。
dhg的第一个答案似乎与尾递归的性能相当,暗示大小通过是超级高效的...事实上,当优化时它的运行速度比尾部快一点 - 在我的硬件上递归一个。
答案 0 :(得分:11)
最干净的两遍解决方案可能只是使用内置的count
方法:
val complete = groupRows.count(_.time == 0)
val counts = (complete, groupRows.size - complete)
但如果在迭代器上使用partition
,则可以一次性完成:
val (complete, incomplete) = groupRows.iterator.partition(_.time == 0)
val counts = (complete.size, incomplete.size)
这是有效的,因为新返回的迭代器在后台链接,并且在一个上调用next
会导致它向前移动原始迭代器,直到找到匹配的元素,但它会记住不匹配的元素其他迭代器,以便它们不需要重新计算。
一次通过解决方案的示例:
scala> val groupRows = List(Row(0), Row(1), Row(1), Row(0), Row(0)).view.map{x => println(x); x}
scala> val (complete, incomplete) = groupRows.iterator.partition(_.time == 0)
Row(0)
Row(1)
complete: Iterator[Row] = non-empty iterator
incomplete: Iterator[Row] = non-empty iterator
scala> val counts = (complete.size, incomplete.size)
Row(1)
Row(0)
Row(0)
counts: (Int, Int) = (3,2)
答案 1 :(得分:3)
我看到你已经接受了答案,但你正确地提到该解决方案将遍历列表两次。有效地实现它的方法是使用递归。
def counts(xs: List[...], complete: Int = 0, incomplete: Int = 0): (Int,Int) =
xs match {
case Nil => (complete, incomplete)
case row :: tail =>
if (row.time == 0) counts(tail, complete + 1, incomplete)
else counts(tail, complete, incomplete + 1)
}
这实际上只是一个自定义的fold
,除了我们使用2个累加器,它们只是Int
s(基元)而不是元组(引用类型)。它也应该与vars一样有效的while循环 - 事实上,字节码应该是相同的。
答案 2 :(得分:2)
好的,受到上述答案的启发,但我真的只希望只通过列表并避免使用GC,我认为,面对缺乏直接的API支持,我会将其添加到我的中央库代码中:
class RichList[T](private val theList: List[T]) {
def partitionCount(f: T => Boolean): (Int, Int) = {
var matched = 0
var unmatched = 0
theList.foreach(r => { if (f(r)) matched += 1 else unmatched += 1 })
(matched, unmatched)
}
}
object RichList {
implicit def apply[T](list: List[T]): RichList[T] = new RichList(list)
}
然后在我的应用程序代码中(如果我已导入隐式),我可以编写无var表达式:
val (complete, incomplete) = groupRows.partitionCount(_.time != 0)
得到我想要的:一个优化的GC友好程序,可以防止我用vars污染程序的其余部分。
然而,我随后看到了Luigi的基准测试,并将其更新为:
var实现肯定要快得多,即使Luigi的例程应该是相同的(正如人们所期望的那样,优化的尾递归)。令人惊讶的是,dhg的双通道原始版本与尾部递归版本一样快(如果编译器优化开启稍快)。我不明白为什么。
答案 3 :(得分:2)
这个怎么样?没有进口税。
import scala.collection.generic.CanBuildFrom
import scala.collection.Traversable
import scala.collection.mutable.Builder
case class Count(n: Int, total: Int) {
def not = total - n
}
object Count {
implicit def cbf[A]: CanBuildFrom[Traversable[A], Boolean, Count] = new CanBuildFrom[Traversable[A], Boolean, Count] {
def apply(): Builder[Boolean, Count] = new Counter
def apply(from: Traversable[A]): Builder[Boolean, Count] = apply()
}
}
class Counter extends Builder[Boolean, Count] {
var n = 0
var ttl = 0
override def +=(b: Boolean) = { if (b) n += 1; ttl += 1; this }
override def clear() { n = 0 ; ttl = 0 }
override def result = Count(n, ttl)
}
object Counting extends App {
val vs = List(4, 17, 12, 21, 9, 24, 11)
val res: Count = vs map (_ % 2 == 0)
Console println s"${vs} have ${res.n} evens out of ${res.total}; ${res.not} were odd."
val res2: Count = vs collect { case i if i % 2 == 0 => i > 10 }
Console println s"${vs} have ${res2.n} evens over 10 out of ${res2.total}; ${res2.not} were smaller."
}
答案 4 :(得分:0)
使用可变累加器模式稍微整洁一些,特别是如果你可以重新使用你的累加器:
case class Accum(var complete = 0, var incomplete = 0) {
def inc(compl: Boolean): this.type = {
if (compl) complete += 1 else incomplete += 1
this
}
}
val counts = groupRows.foldLeft( Accum() ){ (a, row) => a.inc( row.time == 0 ) }
如果你真的想,你可以隐藏你的vars作为私人;如果没有,你仍然比vars模式更加独立。
答案 5 :(得分:0)
您可以使用这样的差异来计算它:
def counts(groupRows: List[Row]) = {
val complete = groupRows.foldLeft(0){ (pair, row) =>
if(row.time == 0) pair + 1 else pair
}
(complete, groupRows.length - complete)
}