【问题标题】:Queues in Racket?球拍中的队列?
【发布时间】:2021-12-21 01:43:56
【问题描述】:

我用下面的代码解决了Sum by Factors:

#lang racket
(provide sum-of-divided)

(define (sum-of-divided lst)
  (define (go ps n l)
    (define ((exhaust d) x)
      (define q (/ x d))
      (if (integer? q)
          ((exhaust d) q)
          (if (> x 1) `(,x) '())))
    (if (null? l)
        ps
        (if
          (for/or
            ([p ps])
            #:break (< n (sqr p))
            (= 0 (modulo n p)))
          (go ps (+ n 1) l)
          (go
            (append ps `(,n))
            (+ n 1)
            (append-map (exhaust n) l)))))
  (for*/list
    ([m (go '() 2 (map abs lst))]
     [s `(,(for/fold
       ([a '(0 #f)])
       ([x lst])
       (if (= 0 (modulo x m))
           `(,(+ (car a) x) #t)
           a)))]
     #:when (cadr s))
    `(,m ,(car s))))

令我惊讶的是,在我将 L20 中的 sequence-append 更改为 append 之后,它才通过了时间限制为 12 秒的测试。 documentation for sequence-append 说:

新序列是惰性构造的。

但是,事实证明,这显然意味着除非需要,否则不会连接后续序列。但是当需要它们的元素时,即由sequence-append 产生的序列被消耗得足够远,就会产生与所有先前序列的长度之和呈线性关系的时间成本。正确的?这就是它慢的原因吗?

如果是这样,如何解决它? (在这种情况下,append 的性能已经足够好了,但假设我真的需要一个结构,它至少是一个具有通常复杂性的 FIFO 队列。)在racket 语言中是否有一个很好的替代方案,没有requireing 额外的包(这可能不可用,就像 Codewars 上的情况一样)?差异列表可能(从头开始很容易实现)?

【问题讨论】:

  • 我怀疑所有准引用/取消引用可能比使用“正常”列表构建功能要慢。
  • 有趣的怀疑。我是认真的。为什么会这样?无论如何,这种情况下的关键区别是 append 与 sequence-append。

标签: performance queue concatenation racket lazy-evaluation


【解决方案1】:

我最终使用了迄今为止故意避免使用的显而易见的:可变列表:

#lang racket
(provide sum-of-divided)

(define (sum-of-divided lst)
  (define ps (mcons 0 '()))
  (define t ps)
  (for*/list
    ([m
      (let go ([n 2] [l (map abs lst)])
        (if (null? l)
            (mcdr ps)
            (go
              (+ n 1)
              (if
                (for/or
                  ([p (mcdr ps)])
                  #:break (< n (sqr p))
                  (= 0 (modulo n p)))
                l
                (begin
                  (set-mcdr! t (mcons n '()))
                  (set! t (mcdr t))
                  (remq*
                    '(1)
                    (map
                      (λ (x)
                         (let exhaust ([s x])
                           (define q (/ s n))
                           (if (integer? q)
                               (exhaust q)
                               s)))
                      l)))))))]
     [s `(,(for/fold
       ([a '(0 #f)])
       ([x lst])
       (if (= 0 (modulo x m))
           `(,(+ (car a) x) #t)
           a)))]
     #:when (cadr s))
    `(,m ,(car s))))

我还尝试了使用流的纯函数式方法:

#lang racket
(provide sum-of-divided)

(define primes
  (letrec
    ([ps
      (stream*
        2
        (for*/stream
          ([i (in-naturals 3)]
           #:unless
             (for/or
               ([p ps])
               #:break (< i (sqr p))
               (= 0 (modulo i p))))
        i))])
    ps))

(define (sum-of-divided lst)
  (for/fold
    ([l lst]
     [r '()]
     #:result (reverse r))
    ([d primes])
    #:break (null? l)
    (values
      (remq*
        '(1)
        (map
          (λ (x)
             (let exhaust ([s x])
               (define q (/ s d))
               (if (integer? q)
                   (exhaust q)
                   s)))
          l))
      `(,@(for/fold
          ([a 0]
           [f #f]
           #:result
             (if f
                 `((,d ,a))
                 '()))
          ([n lst])
           (if (= 0 (modulo n d))
               (values (+ a n) #t)
               (values a f)))
        ,@r))))

令人惊讶的是,它总是超时,而上面的命令式从来没有。相信 Racket 实现者至少同样关心功能风格的性能,我很失望。

【讨论】:

  • 没有其他答案?如果它一直这样,我将在周末之后接受我自己的。
猜你喜欢
  • 2018-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-29
  • 2012-10-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多