许多Project Euler问题需要在base10和base2中操纵整数及其数字。虽然我在数字列表中转换整数或将base10转换为base2(或其数字列表)没有问题,但我经常发现重复进行此类转换时性能很差。
以下是一个例子:
首先,这是我的典型转换:
#lang racket
(define (10->bin num)
(define (10->bin-help num count)
(define sq
(expt 2 count))
(cond
[(zero? count) (list num)]
[else (cons (quotient num sq) (10->bin-help (remainder num sq) (sub1 count)))]
)
)
(member 1 (10->bin-help num 19)))
(define (integer->lon int)
(cond
[(zero? int) empty]
[else (append (integer->lon (quotient int 10)) (list (remainder int 10)))]
)
)
接下来,我需要一个函数来测试数字列表是否是回文
(define (is-palindrome? lon)
(equal? lon (reverse lon)))
最后,我需要将所有base10整数加在base2和base10的最大值下,这些是palindromes。这是累加器式函数:
(define (sum-them max)
(define (sum-acc count acc)
(define base10
(integer->lon count))
(define base2
(10->bin count))
(cond
[(= count max) acc]
[(and
(is-palindrome? base10)
(is-palindrome? base2))
(sum-acc (add1 count) (+ acc count))]
[else (sum-acc (add1 count) acc)]))
(sum-acc 1 0))
常规递归版本:
(define (sum-them* max)
(define base10
(integer->lon max))
(define base2
(10->bin max))
(cond
[(zero? max) 0]
[(and
(is-palindrome? base10)
(is-palindrome? base2))
(+ (sum-them* (sub1 max)) max)]
[else (sum-them* (sub1 max))]
)
)
当我将这两个最后一个函数中的任何一个应用到1000000时,我需要10秒以上才能完成。递归版本似乎比累加器版本快一点,但差别可以忽略不计。
有什么方法可以改进这段代码,或者我只是必须接受这是数字运算的风格,而Racket并不是特别适合?
到目前为止,我已经考虑过用类似的整数 - >向量替换整数 - > lon的可能性,因为我期望vector-append比追加更快,但后来我不得不应用反向以后。
答案 0 :(得分:1)
你是否有机会在DrRacket中运行这些时间? IDE会慢慢减慢速度,特别是如果你碰巧打开了调试和/或分析,所以我建议你从命令行进行这些测试。
此外,您通常可以改进蛮力方法。例如,你可以在这里说我们只需要考虑奇数,因为当表示为二进制时,偶数数永远不是回文(尾随0,但你表示它们的方式从来就不是标题0)。无论算法如何,这都将执行时间除以2。
您的代码会在2.4秒内在我的笔记本电脑上运行。我使用字符串和内置函数编写了一个替代版本,运行时间为0.53秒(包括Racket启动; Racket中的执行时间 0.23秒):
#!/usr/bin/racket
#lang racket
(define (is-palindrome? lon)
(let ((lst (string->list lon)))
(equal? lst (reverse lst))))
(define (sum-them max)
(for/sum ((i (in-range 1 max 2))
#:when (and (is-palindrome? (number->string i))
(is-palindrome? (number->string i 2))))
i))
(time (sum-them 1000000))
产量
pu@pumbair: ~/Projects/L-Racket time ./speed3.rkt
cpu time: 233 real time: 233 gc time: 32
872187
real 0m0.533s
user 0m0.472s
sys 0m0.060s
我很确定拥有更多Racket剖析经验的人会提出更快的解决方案。
所以我可以给你以下提示:
N.B。您的10->bin
函数会返回值#f
的{{1}},我猜它应该返回0
。
答案 1 :(得分:1)
您是否考虑使用任何Racket bitwise operations获取位列表?例如,
(define (bits n)
(let loop ((n n) (acc '()))
(if (= 0 n)
acc
(loop (arithmetic-shift n -1) (cons (bitwise-and n 1) acc)))))
> (map bits '(1 3 4 5 7 9 10))
'((1) (1 1) (1 0 0) (1 0 1) (1 1 1) (1 0 0 1) (1 0 1 0))
看看是否能提高速度是很有趣的。我希望它会有所帮助,因为您的10->bin
程序目前会调用expt
,quotient
和remainder
,而比特移位则取决于所使用的表示形式编译器可能会更有效率。
你的integer->lon
也使用了比你需要的更多的内存,因为append
正在复制每一步的大部分结果。这很有趣,因为您已经在bin->10
中使用了更节省内存的方法。这样的事情更有效:
(define (digits n)
(let loop ((n n) (acc '()))
(if (zero? n)
acc
(loop (quotient n 10) (cons (remainder n 10) acc)))))
> (map digits '(1238 2391 3729))
'((1 2 3 8) (2 3 9 1) (3 7 2 9))
所有这一切,也许你应该考虑你正在使用的方法。现在看来,你正在遍历数字1 ... MAX,检查每一个是否是回文,如果是,则将它加到总和中。这意味着你正在做一些MAX数字,总而言之。而不是检查回文数字,为什么不直接在一个基地生成它们,然后检查它们是否是另一个的回文。即,不要检查1 ... MAX,检查:
这个列表是所有二元回文,只有一些是十进制回文。如果你可以使用bit-twiddling技术生成二进制回文(因此你实际上正在处理整数),很容易将它们写入字符串,并检查字符串是否是回文可能比检查列表是否快得多是一个回文。