哪个是更惯用的方案?

时间:2016-01-23 23:44:40

标签: scheme

我正在经历SICP,对于符号微分器,我提出了两种方法来获取任意数量的参数。

一个返回许多参数的总和:

(make-sum 1 'x 4) -> (+ 4 'x)

(make-sum '(+ 3 x) (** x 2) 5) -> (+ 8 x (** x 2))

而另一个确实:

(make-sum 1 'x 4) ->(+ 1 (+ 'x 4))

(make-sum '(+ 3 x) (** x 2) 5) -> (+ (+3 x) (+ (** x 2) 5))

请注意,make-sum都没有完全正常运行。我的问题不是要弄清楚如何解决这些问题。

首先make-sum

(define (make-sum . ad)
  #|goes through the list ad
  and adds up the numbers, while
  putting non-numeric terms into 
  a separate list, returning a new 
  list with the sum and non-numeric
  term list|#
  (define (accumulate-terms lst)
    (define (accumulate-terms-help lst num-sum other-terms)
      (cond ((null? lst) `(,@other-terms ,num-sum))
            ((number? (car lst)) 
             (accumulate-terms-help (cdr lst)
                                (+ num-sum (car lst))
                                other-terms))
            (else (accumulate-terms-help (cdr lst)
                                     num-sum
                         (cons (car lst) other-terms)))))
    (accumulate-terms-help lst 0 '()))

  #|modified flatten that only flattens
  sub-lists that have sym as their first element|#
  (define (flatten lst sym)
    (cond ((eq? lst '()) '())
          ((list? (car lst))
          (cond ((eq? (caar lst) sym)
             (append (flatten (car lst) sym) (flatten (cdr lst) sym)))
            (else (cons (car lst) (flatten (cdr lst) sym)))))     
          (else
           (cons (car lst) (flatten (cdr lst) sym)))))

  #|flattens out all addition sub terms
  accumulates what is left and then filters
  any zeroes|#
  (let* ()
    (define ret
      (filter (lambda (p)
            (not (eq? p 0)))
              (cond ((> (length ad) 1)
                 `(+ ,@(accumulate-terms
                     (filter (lambda (q)
                           (not (eq? q '+)))
                         (flatten ad '+)))))
                    (else ad))))

    (cond ((> (length ret) 2)
           ret)
          (else (cadr ret)))))

第二次总结

(define (make-sum . an)
  (cond
    ((equal? (length an) 1)
      (let ((it (car an)))
        (cond
          ((number? it) it)
          ((variable? it) `',it)
          ((sum? it) (eval `(make-sum ,@it)))
          (else it))))
    (else
      (let ((cur (car an))
           (rest (cdr an)))
        (cond
          ((number? cur)
             `(+ ,cur ,(eval `(make-sum ,@rest))))
          ((variable? cur)
             `(+ ',cur ,(eval `(make-sum ,@rest))))     
          ((sum? cur)
             (let ((ad (addend cur))
                   (ag (augend cur)))
               (cond
               #|if both the addend and augend of cur
               are numbers, returns the sum|#
                 ((and (number? ad)
                   (number? ag))
                   `(+ ,(+ ad ag)
                   ,(eval `(make-sum ,@rest))))

               #|if the addend of cur is a number
               and the augend of cur is a sum|#
                 ((and (number? ad)
                   (sum? ag))
                   (let ((adg (addend ag))
                         (agg (augend ag)))
                     (cond
                       ((number? adg)
                    `(+ ,(+ ad adg)
                        ,(eval `(make-sum agg ,@rest))))

                       ((number? agg)
                    `(+ ,(+ ad agg)
                        ,(eval `(make-sum adg ,@rest))))

                       (else `(+ ,ad
                         ,(eval `(make-sum ,ag
                                   ,@rest)))))))

                 (else `(+ ,cur (eval `(make-sum ,@rest))))))))))))

所以我的问题是,哪种方法是" schemier"做事的方式, 展平和过滤列表以将其转换为正确的列表, 或手动递归通过一个递归的每个级别的规则? 第一个示例的优势在于其输出比第二个(make-sum 'a 'b 'c) -> (+ a b c)更具可读性(make-sum 'a 'b 'c) -> (+ a (+ b c))。第二个优势是你可以更容易地得到导数,只使用两个函数addendaugend来选择运算符,导数的表达方式与你在微积分课。第一个例子更难以使用衍生物,需要将deriv映射到每个术语和一些过滤器以确保正确的输出。

修改 此外,我不喜欢我必须使用eval这么多,但这是我能想到将一个解压缩的列表输入到像

这样的功能的唯一方法
(eval `(foo ,@lst)) ;foo takes any number of arguments

此外:

`',var ; for quoting the result from var
       ; when this term is inside of a qq

看起来似乎有更好的方法

1 个答案:

答案 0 :(得分:1)

在Scheme中,惯用的方法是使用高阶函数 - 根据mapfilterfoldlfoldr,{{1}来构建解决方案等等(还有更多!)这将导致更短的解决方案,而不必显式循环每个列表。

另外,避免滥用准引用,拼接和flatten :-)。您可以在不使用它们的情况下重写所有代码,但在本书中,作者尚未涵盖它们。例如,这个:

eval

等同于:

(eval `(foo ,@lst))

以上是首选方式。同样,这没有多大意义:

(apply foo lst)

如果`',var 已经是一个符号,那么请保留它,您不必再次引用它。一句话:保持简单,你的代码无缘无故过于复杂。