提高将数字转换为列表的性能,并将数字10转换为基数2

时间:2013-11-06 20:33:30

标签: performance racket

许多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比追加更快,但后来我不得不应用反向以后。

2 个答案:

答案 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程序目前会调用exptquotientremainder,而比特移位则取决于所使用的表示形式编译器可能会更有效率。

你的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,检查:

  • 1
  • 11
  • 101,和111
  • 1001,和1111
  • 10001,10101,11011和11111,
  • 等等,直到数字太大。

这个列表是所有二元回文,只有一些是十进制回文。如果你可以使用bit-twiddling技术生成二进制回文(因此你实际上正在处理整数),很容易将它们写入字符串,并检查字符串是否是回文可能比检查列表是否快得多是一个回文。