【发布时间】:2015-05-31 21:46:05
【问题描述】:
我正在尝试通过重写基本练习来学习 Scala 和函数式编程思想。目前,我对生成素数“试除法”的幼稚方法遇到了麻烦。
下面描述的问题是我无法在函数式风格保持效率上重写众所周知的算法,因为我没有合适的不可变数据结构,如 List,但不仅在头部,而且在最后。
我开始编写 java 代码,该代码对每个奇数通过已找到的素数(受测试值的平方根限制)测试其可除性 - 如果未找到除数,则将其添加到列表的末尾。
List<Integer> primes = new ArrayList<>();
primes.add(2);
int cur = 3;
while (primes.size() < 100000) {
for (Integer x : primes) {
if (x * x > cur) {
primes.add(cur);
break;
}
if (cur % x == 0) {
break;
}
}
cur += 2;
}
现在我尝试以“功能方式”重写它——使用递归而不是循环没有问题,但我坚持使用不可变集合。核心思想如下:
def primes(n: Int) = {
@tailrec
def divisibleByAny(x: Int, list: List[Int]): Boolean = {
if (list.isEmpty) false else {
val h = list.head
h * h <= x && (x % h == 0 || divisibleByAny(x, list.tail))
}
}
@tailrec
def morePrimes(from: Int, prev: List[Int]): List[Int] = {
if (prev.size == n) prev else
morePrimes(from + 2, if (divisibleByAny(from, prev)) prev else prev :+ from)
}
morePrimes(3, List(2))
}
但它慢 - 如果我理解正确,因为添加到不可变列表末尾的操作需要创建整个内容的新副本。
我搜索了文档以找到更合适的数据结构并尝试用不可变队列替换列表,因为据说:
将项目添加到队列总是需要花费 O(1) ... 删除项目平均需要 O(1)。
但它仍然更慢:
def primes(n: Int) = {
@tailrec
def divisibleByAny(x: Int, list: Queue[Int]): Boolean = {
if (list.isEmpty) false else {
val (h, t) = list.dequeue
h * h <= x && (x % h == 0 || divisibleByAny(x, t))
}
}
@tailrec
def morePrimes(from: Int, prev: Queue[Int]): Queue[Int] = {
if (prev.size == n) prev else
morePrimes(from + 2, if (divisibleByAny(from, prev)) prev else prev.enqueue(from))
}
morePrimes(3, Queue(2))
}
出了什么问题或者我错过了什么?
附:我相信还有其他算法可以生成更适合函数式风格的素数。我想我见过一些纸。但现在我对这个感兴趣,或者更准确地说是对合适的数据结构的存在感兴趣。
【问题讨论】:
-
Vectors 添加、删除(从两端)和检索的成本为 O(1)。除此之外,添加到列表的开头是 O(1) 那么为什么不这样做呢? -
没关系,我明白为什么了。
标签: algorithm scala data-structures functional-programming