Performance Scala PriorityQueue阵列[Int]性能问题,比较函数复杂(需要缓存)

Performance Scala PriorityQueue阵列[Int]性能问题,比较函数复杂(需要缓存),performance,scala,caching,priority-queue,large-data,Performance,Scala,Caching,Priority Queue,Large Data,这个问题涉及到Scala PriorityQueue[Array[Int]]在大型数据集上的性能。需要执行以下操作:排队、出列和筛选。目前,我的实施情况如下: 对于Array[Int]类型的每个元素,都有一个复杂的求值函数:我不知道如何更有效地编写它,因为它排除了位置0 def eval_fun(a : Array[Int]) = if(a.size < 2) 3 else { var ret = 0 var i = 1 while(i < a.siz

这个问题涉及到Scala PriorityQueue[Array[Int]]在大型数据集上的性能。需要执行以下操作:排队、出列和筛选。目前,我的实施情况如下:

对于Array[Int]类型的每个元素,都有一个复杂的求值函数:我不知道如何更有效地编写它,因为它排除了位置0

def eval_fun(a : Array[Int]) =
  if(a.size < 2) 3
  else {
    var ret = 0
    var i = 1
    while(i < a.size) {
      if((a(i) & 0x3) == 1) ret += 1
      else if((a(i) & 0x3) == 3) ret += 3
      i += 1
    }
    ret / a.size
  }
优先级队列定义为:

val pq: scala.collection.mutable.PriorityQueue[Array[Int]] = PriorityQueue()
问题:

有没有更优雅、更有效的方法来编写这样的评估函数?我正在考虑使用fold,但是fold不能排除位置0。 是否有数据结构生成具有唯一元素的priorityqueue?在每次排队操作后应用筛选器操作是无效的。 是否有一种缓存方法来减少计算量?因为在向队列中添加新元素时,每个元素可能需要再次由eval_fun进行求值,如果每个元素的求值值都可以缓存,则不需要这样做。另外,我应该提到,两个不同的元素可能具有相同的评估值。 是否有一种更有效的数据结构而不使用泛型类型?因为如果元素的大小达到10000,大小达到1000,那么性能会非常慢。
谢谢。

1如果您想在这里获得最佳性能,我会坚持使用while循环,即使它不是非常优雅。否则,如果在阵列上使用视图,则可以在进入折叠之前轻松删除第一个元素:

2您可以维护两种结构,优先级队列和集合。两者结合在一起可以得到一个排序集。。。因此,您可以使用collection.immutable.SortedSet,但标准库中没有可变变量。是否希望基于优先级函数或实际数组内容实现相等?因为在后一种情况下,您将无法对每个插入逐个元素比较数组,从而取消缓存优先级函数值的效果

3只需将计算出的优先级与数组一起放入队列中。即

implicit val ord = Ordering.by[(Int, Array[Int]), Int](_._1)
val pq = new collection.mutable.PriorityQueue[(Int, Array[Int])]
pq += eval_fun(a) -> a

1如果您想在这里获得最佳性能,我会坚持使用while循环,即使它不是非常优雅。否则,如果在阵列上使用视图,则可以在进入折叠之前轻松删除第一个元素:

2您可以维护两种结构,优先级队列和集合。两者结合在一起可以得到一个排序集。。。因此,您可以使用collection.immutable.SortedSet,但标准库中没有可变变量。是否希望基于优先级函数或实际数组内容实现相等?因为在后一种情况下,您将无法对每个插入逐个元素比较数组,从而取消缓存优先级函数值的效果

3只需将计算出的优先级与数组一起放入队列中。即

implicit val ord = Ordering.by[(Int, Array[Int]), Int](_._1)
val pq = new collection.mutable.PriorityQueue[(Int, Array[Int])]
pq += eval_fun(a) -> a

通常,您可以使用尾部递归循环,这些循环更为惯用:

def eval(a: Array[Int]): Int =
  if (a.size < 2) 3
  else {
    @annotation.tailrec
    def loop(ret: Int = 0, i: Int = 1): Int =
      if (i >= a.size) ret / a.size
      else {
        val mod3 = (a(i) & 0x3)
        if (mod3 == 1) loop(ret + 1, i + 1)
        else if (mod3 == 3) loop(ret + 3, i + 1)
        else loop(ret, i + 1)
      }
    loop()
  }
然后,您可以使用它初始化缓存的优先级值:

case class PriorityArray(a: Array[Int]) {
  lazy val priority = if (a.size < 2) 3 else {
    @annotation.tailrec
    def loop(ret: Int = 0, i: Int = 1): Int =
      if (i >= a.size) ret / a.size
      else {
        val mod3 = (a(i) & 0x3)
        if (mod3 == 2) loop(ret, i + 1)
        else loop(ret + mod3, i + 1)
      }
    loop()
  }
}
您可能还注意到,我删除了一个冗余的&op,当它等于2时,只有一个条件for,而不是对1和3进行两次检查–这些应该会产生最小的影响


与刚刚提出的0的建议没有太大区别。

好吧,通常可以使用尾部递归循环。这些循环更为惯用:

def eval(a: Array[Int]): Int =
  if (a.size < 2) 3
  else {
    @annotation.tailrec
    def loop(ret: Int = 0, i: Int = 1): Int =
      if (i >= a.size) ret / a.size
      else {
        val mod3 = (a(i) & 0x3)
        if (mod3 == 1) loop(ret + 1, i + 1)
        else if (mod3 == 3) loop(ret + 3, i + 1)
        else loop(ret, i + 1)
      }
    loop()
  }
然后,您可以使用它初始化缓存的优先级值:

case class PriorityArray(a: Array[Int]) {
  lazy val priority = if (a.size < 2) 3 else {
    @annotation.tailrec
    def loop(ret: Int = 0, i: Int = 1): Int =
      if (i >= a.size) ret / a.size
      else {
        val mod3 = (a(i) & 0x3)
        if (mod3 == 2) loop(ret, i + 1)
        else loop(ret + mod3, i + 1)
      }
    loop()
  }
}
您可能还注意到,我删除了一个冗余的&op,当它等于2时,只有一个条件for,而不是对1和3进行两次检查–这些应该会产生最小的影响

与刚刚提出的0的建议没有太大区别。

我的答案:

如果评估非常关键,请保持原样。使用递归可能会获得更好的性能不知道为什么,但这种情况确实会发生,但使用几乎任何其他方法都肯定会获得更差的性能

不,没有,但您只需修改出列操作即可实现:

def distinctDequeue[T]q:PriorityQueue[T]:T={ val结果=q.dequeue 而q.head==结果q.dequeue 后果 }

否则,您必须保留第二个数据结构,以便跟踪是否添加了元素。不管怎样,等号都很重,但我有一个建议,在下一个项目中让它更快

但是,请注意,这需要以其他方式解决成本函数上的关系

像0建议的那样,将成本放在优先级队列上。但是如果有帮助的话,您也可以在函数上保留一个缓存。我想试试这样的东西:

val evalMap=scala.collection.mutable.HashMapWrappedArray[Int],Int def eval_funa:数组[Int]= ifa.尺寸<2 3 else evalMap.getOrElseUpdatea{ var-ret=0 变量i=1 whilei 导入scala.math.Ordering.Implicits_ val pq=new collection.mutable.PriorityQueue[Int,WrappedArray[Int]] pq+=eval_funa->a:WrappedArray[Int]

请注意,我没有创建特殊的排序-我使用的是标准排序,以便WrappedArray将打破联系。包装数组的成本很低,您可以使用.Array将其返回,但另一方面,您可以获得以下结果:

通过比较阵列本身,将打破联系。如果成本中没有太多的联系,这应该足够好了。如果有,则向元组中添加其他内容,以帮助在不比较数组的情况下打破关系

这意味着所有相等的元素将保持在一起,这将使您能够同时将所有元素排在队列之外,给人留下只保留一个元素的印象

实际上,这个等式是有效的,因为WrappedArray和Scala序列一样进行比较

我不明白你说的第四点是什么意思。

我的答案:

如果评估非常关键,请保持原样。使用递归可能会获得更好的性能不知道为什么,但这种情况确实会发生,但使用几乎任何其他方法都肯定会获得更差的性能

不,没有,但您只需修改出列操作即可实现:

def distinctDequeue[T]q:PriorityQueue[T]:T={ val结果=q.dequeue 而q.head==结果q.dequeue 后果 }

否则,您必须保留第二个数据结构,以便跟踪是否添加了元素。不管怎样,等号都很重,但我有一个建议,在下一个项目中让它更快

但是,请注意,这需要以其他方式解决成本函数上的关系

像0建议的那样,将成本放在优先级队列上。但是如果有帮助的话,您也可以在函数上保留一个缓存。我想试试这样的东西:

val evalMap=scala.collection.mutable.HashMapWrappedArray[Int],Int def eval_funa:数组[Int]= ifa.尺寸<2 3 else evalMap.getOrElseUpdatea{ var-ret=0 变量i=1 whilei 导入scala.math.Ordering.Implicits_ val pq=new collection.mutable.PriorityQueue[Int,WrappedArray[Int]] pq+=eval_funa->a:WrappedArray[Int]

请注意,我没有创建特殊的排序-我使用的是标准排序,以便WrappedArray将打破联系。包装数组的成本很低,您可以使用.Array将其返回,但另一方面,您可以获得以下结果:

通过比较阵列本身,将打破联系。如果成本中没有太多的联系,这应该足够好了。如果有,则向元组中添加其他内容,以帮助在不比较数组的情况下打破关系

这意味着所有相等的元素将保持在一起,这将使您能够同时将所有元素排在队列之外,给人留下只保留一个元素的印象

实际上,这个等式是有效的,因为WrappedArray和Scala序列一样进行比较


我不明白你说的第四点是什么意思。

pq+=eval\u funa->a是不正确的。例如,val a=阵列1,2,3,4;val b=阵列1,2,3,4;在pq+之后=eval_funa->a;pq+=eval_funb->b;有两个要素。实际上,我们只需要1个元素在1:sum中,a应该是a,sum。不管怎样,谢谢你。第二:既然这涉及到排队和退队,那么如何有效地做到这一点呢?thanks@Tianyi:是的,将有两个元素。优先级队列允许您添加具有相同优先级的多个元素。看起来你真的想要一个排序集。忘记优先级队列吧。请注意,排序顺序是相反的。head将为您提供优先级最低的元素,因此请确保使用-eval_fun进行排序。另外,fold函数确实需要sum,a,参见scala API文档。然后它就不正确了:val a=Array1,2,3,4a:Array[Int]=Array1,2,3,4 a.view.drop1.foldLeft0 sum,a=>sum+a&0x03匹配{case 0x01=>1 case 0x03=>3 case\u=>0}/a.sizeres0:Int=0同样,我在代码中尝试了你的想法。它似乎慢了两倍。我不确定问题出在哪里。SortedSet存在一些问题,例如:val a=Array1;b=阵列1;在集合中,他们认为它们是不同的元素。PQ+= ValueFuna-> A是不正确的。例如,val a=阵列1,2,3,4;val b=阵列1,2,3,4;在pq+之后=eval_funa->a;pq+=eval_funb->b;有两个要素。实际上,我们只需要1个元素在1:sum中,a应该是a,sum。不管怎样,谢谢你。第二:既然这涉及到排队和退队,那么如何有效地做到这一点呢?thanks@Tianyi:是的,将有两个元素。优先级队列允许您添加具有相同优先级的多个元素。看起来你真的想要一个排序集。忘记优先级队列吧。请注意,排序顺序是相反的。丙烯酸羟乙酯
d将为您提供优先级最低的元素,因此请确保使用-eval_fun进行排序。另外,fold函数确实需要sum,a,参见scala API文档。然后它就不正确了:val a=Array1,2,3,4a:Array[Int]=Array1,2,3,4 a.view.drop1.foldLeft0 sum,a=>sum+a&0x03匹配{case 0x01=>1 case 0x03=>3 case\u=>0}/a.sizeres0:Int=0同样,我在代码中尝试了你的想法。它似乎慢了两倍。我不确定问题出在哪里。SortedSet存在一些问题,例如:val a=Array1;b=阵列1;在集合中,他们认为它们是不同的元素。谢谢你的回答。我还有两个问题。1懒惰的方法比foldLeft好吗?2如何消除同一阵列?例如:val a=Array1,2,3;val b=Array1,2,3.well,array.tail将导致创建一个大小为-1的新数组,如果您不使用SCIS指出的视图,那么递归版本将避免这种情况。否则,在方法上不应该有太大的差异,但匹配可能会导致部分函数的双重求值问题,一次用于定义,另一次用于应用。对于第二本,你需要一本某种形式的回忆录,这本身就值得一个问题。你对回忆录有什么建议吗?谷歌的番石榴有一个非常简单的实习生。不过,对于纯scala版本,我在前面准备了一个:感谢您的分享。然而,我仍然不清楚如何使用你们的课堂。你能给我举个例子吗?谢谢你的回答。我还有两个问题。1懒惰的方法比foldLeft好吗?2如何消除同一阵列?例如:val a=Array1,2,3;val b=Array1,2,3.well,array.tail将导致创建一个大小为-1的新数组,如果您不使用SCIS指出的视图,那么递归版本将避免这种情况。否则,在方法上不应该有太大的差异,但匹配可能会导致部分函数的双重求值问题,一次用于定义,另一次用于应用。对于第二本,你需要一本某种形式的回忆录,这本身就值得一个问题。你对回忆录有什么建议吗?谷歌的番石榴有一个非常简单的实习生。不过,对于纯scala版本,我在前面准备了一个:感谢您的分享。然而,我仍然不清楚如何使用你们的课堂。你能给我举个例子吗?独特是另一个问题。val a=阵列1,2;val b=阵列1,2,0;val c=阵列1,2;在添加到优先级队列之后,它应该是类似于PQArray1,2,Array1,2,0的唯一性是另一个问题。val a=阵列1,2;val b=阵列1,2,0;val c=阵列1,2;在添加到优先级队列之后,它应该是类似于PQArray1,2,Array1,2,0的东西