【问题标题】:Iterative range function in Scheme/LispScheme/Lisp 中的迭代范围函数
【发布时间】:2016-05-25 06:11:58
【问题描述】:

我想实现范围函数 (start, end, inc),它生成从开始到结束的所有自然数,增量为 inc。 这是第一个版本:

(define (gen start end step)
  (if (>= start end)
  '()
  (cons start (gen (+ step start) end step))))

问题是,这不是尾递归的。再试一次:

(define (gen-iter start end step acc)
  (if (>= start end)
    acc
    (gen-iter (+ step start) end step (cons start acc))))

但这会以相反的顺序生成一个列表:) 所以,是的,我可以用 O(n) 反转它,并且很高兴,但我有点卡在这里,试图创建一个函数,该函数将构造一个从头到尾具有正确顺序的列表,不附加每次迭代,因为追加成本很高。 还有一个内置的列表,但我不知道它是如何操作的。

【问题讨论】:

  • 你可以使用队列:rosettacode.org/wiki/Queue/Usage#Racket
  • 规范的方案方式是使用reverse,我敢说这不会成为你的应用程序的瓶颈。至于 Racket 是怎么做的,请使用 Dr Racket 中的“打开定义文件”。

标签: scheme range lisp racket


【解决方案1】:

并不总是那么容易看到它,但“reverse”是这里的一个关键字。要使其迭代,您需要反向执行实际工作:

(define (my-range start end step)      
  (define (helper n acc)
    (if (= end n)
        (cons n acc)
        (helper (- n step) (cons n acc))))

  (define actual-end end) ; this needs improvement
  (helper actual-end '()))

就像球拍中的range 一样,你应该得到这些结果:

(my-range 1 10 1)  ; ==> (1 2 3 4 5 6 7 8 9)
(my-range 10 1 -1) ; ==> (10 9 8 7 6 5 4 3 2)

要得到这个正确的actual-end 需要根据数学找到正确的值。

(my-range 1 10 2)  ; ==> (1 3 5 7 9)  (actual-end should be 9)
(my-range 10 1 -2) ; ==> (10 8 6 4 2) (actual-end should be 2)

我猜你可以使用ceiling 过程以及正常的数学过程来得到正确的结果。

【讨论】:

    【解决方案2】:

    正如 uselpa 在评论中所说,解决方案是在最后使用reverse。扩展后,range 的球拍实现与 reverse 的结果非常相似,是您的 gen-iter 函数的结果,除了某些您可能没有考虑过的情况。

    range在球拍中的实际实现使用(for/list ([i (in-range start end step)]) i),但是那个扩展成的其实相当于

    (reverse
     (for/fold ([fold-var null])
               ([i (in-range start end step)])
       (cons i fold-var)))
    

    除了它使用alt-reverse 函数而不是reverse,它使用for/fold/derived 来更好地报告错误。如果你扩展它,它在一些简化后相当于这个(删除不必要的let-values包装器,错误检查等)

    (reverse
     (let ([start start] [end end] [inc step])
       (let for-loop ([fold-var null] [pos start])
         (if (if (>= step 0)
                 (< pos end)
                 (> pos end))
             (let ([i pos])
               (let ([fold-var (cons i fold-var)])
                 (for-loop
                  fold-var
                  (+ pos inc))))
             fold-var))))
    

    命名的 let 相当于定义一个这样的辅助函数:(在替换一些 lets 并将辅助函数提升到 range 函数之外)

    (define (range start end step)
      (reverse
       (range-reversed null start end step)))
    (define (range-reversed fold-var pos end step)
      (if (if (>= step 0)
              (< pos end)
              (> pos end))
          (range-reversed
           (cons pos fold-var)
           (+ pos step)
           end
           step)
          fold-var))
    

    这与您的解决方案仍然不同,因为您的解决方案假定step 是正数,而这个即使step 是负数也有效。如果step 为正,则if 条件将是(&lt; pos end),而不是嵌套的if。

    它也使用(&lt; pos end),你使用(&gt;= pos end),但它也会切换 if 情况。如果(&lt; x y)(not (&gt;= x y)) 相同,则这等效于您的解决方案,因为(if (not a) b c) 等效于(if a c b)。但是,至少有一种情况我能想到这是不正确的,startend+nan.0。在这种情况下,球拍的 range 函数将返回一个空列表,而您的解决方案将进入无限循环。

    【讨论】:

      猜你喜欢
      • 2019-11-03
      • 1970-01-01
      • 2017-06-07
      • 2010-10-10
      • 1970-01-01
      • 2012-09-15
      • 2012-04-05
      • 2011-02-22
      • 2019-10-07
      相关资源
      最近更新 更多