微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么这个 Scala 函数使用地图比使用列表运行得更快?

如何解决为什么这个 Scala 函数使用地图比使用列表运行得更快?

我已经实现了 PapadimitrIoU 的算法来解决 Scala 中的 two-satisfiability 问题。为了提高我的算法的速度,我首先进行了一个缩减步骤,它消除了所有子句中的一个值在整个子句集中只出现(异或)否定或非否定的所有子句。

该算法是一种局部搜索算法,需要在每个循环中设置一个随机初始状态。我不是完全随机的,而是从减少步骤中知道某些位的状态。因此,我的代码生成一个初始状态的数组缓冲区,其输入是位集的总长度,以及从缩减步骤计算出的约束列表。

最初我的实现只是使用列表来检查特定位是否存在约束,或者我是否需要随机生成位:

// Is bit number x false (negX = true)?
case class Constraint(negX: Boolean,x: Int) extends Ordered[Constraint] {
  def compare(that: Constraint) = this.x - that.x
}

private def getinitial(len: Int,allConstraints: List[Constraint]): ArrayBuffer[Boolean] = {
  var constraints = allConstraints.sorted
  val buff = ArrayBuffer.fill(len)(false)
  for (i <- 0 to (len - 1)) {
    if (constraints.length > 0 && constraints.head.x == i) {
      buff(i) = !constraints.head.negX
      constraints = constraints.tail
    } else {
      buff(i) = math.random < 0.5
    }
  }
  buff
}

以上工作完美,但在分析我的代码后,我发现它很慢。我知道列表的查找通常很慢,因为每次要搜索一个值时都必须遍历整个列表,但我认为当我对列表进行排序并且只是检查头部时,它不会太慢。

无论如何,我尝试了使用 map 的第二个实现:

private def getinitial(len: Int,allConstraints: List[Constraint]): ArrayBuffer[Boolean] = {
  var constraintMap = allConstraints.map{ case Constraint(negX,x) => (x,negX)}.toMap
  val buff = ArrayBuffer.fill(len)(false)
  for (i <- 0 to (len - 1)) {
    buff(i) = !constraintMap.get(i).getorElse(math.random < 0.5)
  }
  buff
}

令我惊讶的是,这明显更快。所以我的问题是为什么?

我有六个不同的数据集,因此我尝试对最小的数据集(100,000 个变量/子句)和最大的数据集(1,000,000 个变量/子句)进行一些时间分析。进行多次运行,我得到了以下最佳时间(由于算法的性质,时间确实会有所不同,最佳时间的重复次数较少,因此生成初始条件的时间将占主导地位)

/* Small dataset */
// Using List
( scala target/scala-2.13/papadimitrIoU-s-algorithmn_2.13-1.0.jar main1; )  9.71s user 0.16s system 116% cpu 8.451 total
// Using Map
( scala target/scala-2.13/papadimitrIoU-s-algorithmn_2.13-1.0.jar main1; )  3.94s user 0.14s system 301% cpu 1.351 total

/* Large dataset */
// Using List
// ...never finished within 15 minutes
// Using Map
( scala target/scala-2.13/papadimitrIoU-s-algorithmn_2.13-1.0.jar main6; )  72.85s user 1.70s system 428% cpu 17.384 total

解决方法

正确的答案是在评论中,但我的美学家无法避免注意到这在实际惯用的 scala 中看起来有多好:)

def getInitial(len: Int,allConstraints: List[Constraint]) = {
  @tailrec
  def loop(i: Int,constraints: List[Constraint],result: List[Boolean]): List[Boolean] = 
    (i,constraints) match {
      case (0,_) => result.reversed
      case (_,head :: tail) if head.x == i => loop(i - 1,tail,!head.negX :: result)
      case _ => loop(i-1,constraints,Random.nextBoolean :: result)
    }

  loop(len-1,allConstraints.sorted.reversed)
}  

我认为,如果您使用预填充数组作为结果,您也可以不进行排序(变异数组元素很糟糕,但如果性能很重要,并且约束的大小很大,这将减少几个周期:

def getInitial(len: Int,allConstraints: List[Constraint]) = {
  val result = Array.fill(len)(Random.nextBoolean)
  allConstraints.foreach { c => result(c.x) = !c.negX }
  result
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。