【问题标题】:Scala: What is making this algorithm for Collatz Sequences so inefficient?Scala:是什么让 Collat​​z Sequences 的算法如此低效?
【发布时间】:2019-09-01 23:24:28
【问题描述】:

我正在尝试解决collatz problem on project Euler 的变体:

n → n/2(n 为偶数)

n → 3n + 1(n 为奇数)

使用上面的规则并从 13 开始,我们生成以下内容 顺序:

13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1

我的尝试是找出小于的数的最长 Collat​​z 序列 一百万

为此,我在 Scala 中编写了一个蛮力解决方案并对其进行了优化。

虽然优化后的版本可以非常快速有效地解决问题,但蛮力解决方案从未完成,最终导致内存不足错误:

object LongestCollatz {
  def apply(upperbound: Long) = {
    bruteForce(upperbound)
  }

  def bruteForce(upperBound: Long) = (1l to upperBound)
    .map(toCollatzSequence)
    .map(list => list.length)
    .max

  def toCollatzSequence(start: Long): ListBuffer[Long] = {
    var term = start
    var retList = ListBuffer(start)
    if(start<=1) return retList
    while(term > 1) {
      if(term % 2 == 0) {
        term = term/2l
      } else {
        term = (3l * term) + 1
      }

      retList.addOne(term)
    }
    retList.addOne(1l)
    retList
  }
}

object Solver extends App {
  println("sol is " + LongestCollatz(1000000))
}

使用 visualvm 和一些日志记录,我看到蛮力方法中范围流的每一步都将针对所有 1M 数字完全完成,然后再进入下一步。所以: .map(toCollatzSequence) 正在加载所有 1M 项的序列并将它们保存在堆中,直到它可以到达下一个 .map() 语句以将它们转换为单个数字长度。我可以通过修改函数来解决这个问题:

  def bruteForce(upperBound: Long) = (1l to upperBound)
    .map(l => toCollatzSequence(l).length)
    .max

因此将两个 map 语句合并为一个。我的问题是,我把两张地图分开是不是出错了?或者对于此类问题,功能解决方案通常不是一个好主意?

【问题讨论】:

  • 我不是 scala 程序员,但通常在 FP 中你编写函数并将 that 传递给 map。

标签: scala functional-programming visualvm


【解决方案1】:

您应该使用.view 将其转换为非严格集合。

您通过将两个.maps 融合为一个来避免立即列表,但是当您只是调用.max 时,长度列表也是可以避免的。

def bruteForce(upperBound: Long) = (1l to upperBound)
  .view
  .map(toCollatzSequence)
  .map(_.length)
  .max

这应该可以解决暴力破解问题。

【讨论】:

  • George- 我从来不知道view 和强制延迟处理。我一直认为流默认是惰性的,所以这对我来说是个新闻。谢谢你告诉我!
【解决方案2】:

我把两张地图分开是不是出错了?

对多个.map() 调用进行排序很少是一个好主意。将它们结合起来将减少您遍历集合的次数。

对于此类问题,一般来说,功能解决方案不是一个好主意吗?

我不知道你为什么这么说。 不是一个好主意是在想出一个蛮力解决方案后停止。蛮力只是一个起点。

这是一个使用尾递归、惰性求值和无可变数据的解决方案。

@annotation.tailrec
def getLen(n: Long, count: Int = 0): Int =
  if (n < 2) count + 1
  else getLen(if (n % 2 == 0) n / 2 else 3 * n + 1, count + 1)

LazyList.tabulate(1000000)(n => (getLen(n),n)).max._2

【讨论】:

  • 感谢您的回答,正如我在问题中指出的那样,我在蛮力之后开发了优化的解决方案,主要是在寻找有关 Scala 如何处理流的信息
猜你喜欢
  • 1970-01-01
  • 2011-06-23
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 2016-09-13
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
相关资源
最近更新 更多