我对Scala很新!但是,我有Euler Problem 4的以下工作解决方案,我想使用par
,只是为了看看我是否可以这样做:
import scala.math
object Problem4 {
def isPalindrome(x: Int): Boolean = {
val string = x.toString
string.reverseIterator.sameElements(string.iterator)
}
def getPairs(minimum: Int, maximum: Int) = {
for (i <- minimum to maximum view;
j <- minimum to maximum view)
yield (i, j)
}
def getAnswer(numberOfDigits: Int): Int = {
val maximum = math.pow(10, numberOfDigits).toInt
val minimum = math.pow(10, numberOfDigits - 1).toInt
val products = for {
pair <- getPairs(minimum, maximum)
product = pair match { case (i, j) => i * j }
if isPalindrome(product)
} yield product
products.par.max
}
def main(args: Array[String]) {
val answer = getAnswer(4)
println("Problem 4 answer: %s".format(answer))
}
} // object Problem4
Project Euler 4要求输入3位数字,我注意到在我的电脑上找到4位数字的答案需要63秒才能在我的双核系统上使用一个处理器。尽管将par
应用于for
表达式的末尾。
如何使用par
将其并行化?理想情况下,我想找到4位数的答案需要30-40秒。谢谢!
编辑:我非常确定getPairs
会返回View
:
scala> def getPairs(minimum: Int, maximum: Int) = {
| for (i <- minimum to maximum view;
| j <- minimum to maximum view)
| yield (i, j)
| }
getPairs: (minimum: Int, maximum: Int)scala.collection.SeqView[(Int, Int),Seq[_]]
此外,将par
添加到getPairs
调用会返回警告,仍然只使用我的某个处理器,并导致java.lang.OutOfMemoryError: Java heap space
例外:
[info] Loading project definition from M:\programming\testdriveneuler\src\problem4\project
[info] Set current project to euler (in build file:/M:/programming/testdriveneuler/src/problem4/)
[info] Compiling 1 Scala source to M:\programming\testdriveneuler\src\problem4\target\scala-2.9.2\classes...
[warn] M:\programming\testdriveneuler\src\problem4\src\main\scala\Problem4.scala:39: `withFilter' method does not yet exist on scala.collection.parallel.ParSeq[((Int, Int), Int)], using `filter' method instead
[warn] pair <- getPairs(minimum, maximum).par
[warn] ^
[warn] one warning found
编辑:我明确有兴趣计算出2个4位数字乘积的欧拉问题4的答案。作为参考,答案是99000099
。
答案 0 :(得分:3)
如此复杂。它可以用2个函数
完成def isPalindrome(x: Int) = x.toString sameElements x.toString.reverse
def products(min: Int, max: Int) = for {
x <- min to max par;
y <- min to max par;
if isPalindrome(x * y)
} yield (x, y, x * y)
scala> products(100, 999).maxBy(_._3)
res0: (Int, Int, Int) = (913,993,906609)
(min to max).view
返回SeqView
,表示懒惰的集合版本。 (min to max).view.par
返回ParSeq
并行收集。换句话说,在惰性序列上调用par
会强制它进行评估。因此,在这种情况下,你应该选择懒惰和平行。当您从SeqView
转移到ParSeq
时,很难说会执行哪些转化,但这种不必要的复杂性导致OutOfMemoryError
。
是的,for
只是对集合的高阶操作的语法糖。 Des for
循环的Desugared版本将类似于:
(min to max par) flatMap { x =>
(min to max par)
.filter(y => isPalindrome(x * y))
.map(y => x * y)
}
答案 1 :(得分:1)
在调用getPairs时添加.par;
pair <- getPairs(min, max).par
我怀疑(但我的手机无法确定)getPairs方法没有返回视图;因此你的for循环执行你的计算。
验证这一点的一种简单方法是省略最后一行(即products.par.max - for循环的评估),看看你的程序是否仍然执行计算。
答案 2 :(得分:1)
你可以这样并行化:
def isPalindrome (n: Int) = n.toString == n.toString.reverse
val R = 100 until 1000
val xs = for (i <- R; j <- R) yield i * j
val pals = xs.par filter isPalindrome
println (pals max)
(忽略.par
非平行)。但是我发现并行版本在我的双核机器上慢了3-4倍。有时并行化的开销是不值得的。
编辑:对于咯咯笑,这是使用Akka的版本(基于Pi calculation tutorial)。它的性能略快于使用并行集合,如4e6的答案(在我的机器上 8.0s vs 9.1s ),尽管如果你删除了该解决方案,性能几乎相同.par
在第二台发电机上。
import akka.actor._
import akka.routing.RoundRobinRouter
sealed trait Euler4Message
case object Calculate extends Euler4Message
case class Work(range1: Seq[Int], range2: Seq[Int]) extends Euler4Message
case class Result(value: Int) extends Euler4Message
case class FinalResult(value: Int, duration: Long)
class Worker extends Actor {
def calculate(r1: Seq[Int], r2: Seq[Int]): Int = {
def isPalindrome(x: Int) = {
val s = x.toString
s.reverseIterator.sameElements(s.iterator)
}
val pals = for (i <- r1; j <- r2; if isPalindrome(i * j)) yield i * j
pals.max
}
def receive = { case Work(r1, r2) => sender ! Result(calculate(r1, r2)) }
}
class Master(nrOfDigits: Int, nrOfWorkers: Int, chunkSize: Int) extends Actor {
var nrOfResults: Int = 0
var maxResult = 0
var sentAll = false
var nrMessages = 0
val start: Long = System.currentTimeMillis
val min = math.pow(10, nrOfDigits - 1).toInt
val max = math.pow(10, nrOfDigits).toInt
val range = min until max
val workerRouter =
context.actorOf(Props[Worker].withRouter(RoundRobinRouter(nrOfWorkers)))
def receive = {
case Calculate =>
for (i <- range.grouped(chunkSize)) {
// grouped produces an Iterator, so is 'lazy'
workerRouter ! Work(i, range)
nrMessages += 1
}
sentAll = true
case Result(value) =>
if (value > maxResult) maxResult = value
nrOfResults += 1
if (sentAll && nrOfResults == nrMessages) {
println("Result = "+ maxResult
+"\nTime in ms: "+ (System.currentTimeMillis - start))
context.system.shutdown()
}
}
}
object Euler4 extends App {
val master = ActorSystem().actorOf(Props(new Master(4, 4, 50)))
master ! Calculate
}
演员的好处是你可以使用命令式代码并且仍然可以获得并行性。因此,在下面的工作者演员中交换calculate
方法,整个事情在 1.0s (8倍改进)中完成。
我发现它在较大的块大小(尝试1000)时运行速度最快,并且确保您至少拥有与处理器一样多的工作人员。
def calculate(r1: Seq[Int], r2: Seq[Int]): Int = {
def isPalindrome(x: Int) = {
val s = x.toString
s.reverseIterator.sameElements(s.iterator)
}
var max = 0
// count down so that we don't have to check if palindrome so often
var i = r1.last
while (i >= r1.head) {
// for some reason counting down here increases the run-time :/
var j = r2.head
while (j <= r2.last) {
val r = i * j
if (r > max && isPalindrome(r)) max = r
j += 1
}
i -= 1
}
max
}