如何为 Racket 中有效的符号微分函数修复我的幂规则简化函数(使结果更易于阅读)?

How to fix my simplification function for power rule (to make results easier to read) for a working Symbolic differentiation function in Racket?

这是我的区分函数,可以正常工作。

#lang racket

(define (diff x expr) 
  (if (not (list? expr))
      (if (equal? x expr) 1 0) 
  (let ( (operation (car expr)) 
         (u (cadr expr))
         (v (caddr expr)))
       (case operation
          ((+) (list '+ (diff x u) (diff x v))) 
          ((-) (list '- (diff x u) (diff x v))) 
          ((*) (list '+                             
                     (list '* u (diff x v))
                     (list '* v (diff x u))))       
          ((/) (list '/ (list '- (list '* v (diff x u)) (list '* u (diff x v))) 
                     (list '* v v)))                
          ((^) (list '* v (list '* (list '^ u (- v 1)) (diff x u ))))                           
))))

现在我的大部分简化功能也能正常工作,但某处有问题,我认为它在我的电源规则简化器中

(define(simplify expr)
  (if (not (list? expr)) expr
  (let ((operation (car expr))      
       (a (simplify (cadr expr)))   
       (b (simplify (caddr expr)))) 
   (case operation 
        ((+) (if (and (number? a)(= a 0)) b    
                 (if (number? b) (if (= b 0) a 
                                     (+ a b)) 
                 (list operation a b)))) 

        ((-) (if (and (number? a) (= a 0)) (- b)         
                 (if (number? b) (if (= b 0) a  
                                     (- a b))
                 (list operation a b)))) 

        ((*) (cond [(number? a)
                    (cond [(= 1 a) b]
                          [(= 0 a) 0]
                          [else (if (number? b)
                                    (cond [(= b 1) a]
                                          [(= b 0) 0]
                                          [else (* a b)])
                                    (list operation a b))])]
                   [(and (number? b) (= b 0)) 0]
                   [(list operation a b)]))
;The case a/b where b=1 is currently only simplified if a is number. Insert an extra case into the cond expression handling b=1
        ((/) (cond [(number? a)
                    (cond [(= 1 b) a]
                          [(= 0 a) 0]
                          [else (if (number? b)
                                    (cond [(= b 1) a]
                                          [(= b 0) 0]
                                          [else (/ a b)])
                                    (list operation a b))])]
                   [(and (number? b) (= b 0)) 0]; this is where an error should be thrown
                         (cond [(= b 1) 1]
                   [(list operation a b)]))

       ((^) (cond [(number? a)
                   ;if a is 1, 1^x is always 1
                   (cond [(= a 1) 1]
                         [else (if (number? b)
                                   ;if a and b are 0 throw error else anything ^0 is 1.
                                   (cond [(= b 0) (if (= a 0) (error "A and B are both 0, statement undefined!") 1)]
                                         ;if b is 1, x^1 is always x
                                         [(= b 1) a]
                                         ;else a^b
                                         [(expt a b)])
                                   ;a or b are continuations
                                   (list operation a b))])]                                  
                   [else (list operation a b)]))
 ))))

我进行了 运行 多次测试,大部分都通过了,但也有一些没有通过,我不明白为什么。

(simplify '(/ x 1)) ;why is this not working correctly
(simplify '(+ (* (^ x 5) 0) (* 3 (* 5 (* (^ x 4) 1)))) ;not simplifying correctly
(simplify '(* 3 (* (^ x 2) 1))) ;not simplifying correctly
;(simplify '(/ 1 0));not working 
;(simplify '(^ 0 0));this works fine just returns an exception

较短的解决方案

通过使用 Racket 的内在能力来计算数字(并进行正确的错误消息传递,例如除以零错误)

#lang racket

(define (simplify expr)
  (if (not (list? expr)) 
      expr
      (cond ((= 1 (length expr)) (simplify (car expr)))
            ((= 2 (length expr))
             (let ((operation (car expr))
                   (a (simplify (cadr expr))))
               (case operation
                 ((-) (cond ((number? a) (- a))
                            (else (list operation (simplify a)))))
                 ((+) (cond ((number? a) a)
                            (else (simplify a))))
                 (else (error "Diadic operator with only one argument given.")))))
            ((= 3 (length expr))        
             (let ((operation (car expr))        
                   (a (simplify (cadr expr)))     
                   (b (simplify (caddr expr))))   
               (case operation  
                 ((+) (cond ((and (number? a) (number? b)) (+ a b)) 
                            ((number? a) 
                             (cond ((zero? a) (simplify b)) 
                                   (else (list operation (simplify a) (simplify b))))) 
                            ((number? b) 
                             (cond ((zero? b) (simplify a))
                                   (else (list operation (simplify a) (simplify b)))))
                            (else (list operation (simplify a) (simplify b)))))
                 ((-) (cond ((and (number? a) (number? b)) (- a b)) ;; use Racket's ability to subtract
                            ((number? a)
                             (cond ((zero? a) (- (simplify b)))
                                   (else (list operation (simplify a) (simplify b)))))
                            ((number? b)
                             (cond ((zero? b) (simplify a))
                                   (else (list operation (simplify a) (simplify b)))))
                            (else (list operation (simplify a) (simplify b)))))
                 ((*) (cond ((and (number? a) (number? b)) (* a b)) ;; use Racket's ability to mulitpy
                            ((number? a)
                             (cond ((zero? a) 0)
                                   ((= a 1) (simplify b))
                                   (else (list operation (simplify a) (simplify b)))))
                            ((number? b)
                             (cond ((zero? b) 0)
                                   ((= b 1) (simplify a))
                                   (else (list operation (simplify a)(simplify b)))))
                            (else (list operation (simplify a) (simplify b)))))
                 ((/) (cond ((and (number? a) (number? b)) (/ a b)) ;; use Racket's ability to divide
                            ((number? a)
                             (cond ((zero? a) 0)
                                   (else (list operation (simplify a) (simplify b)))))
                            ((number? b)
                             (cond ((zero? b) (error "Divison by 0, statement undefined!"))
                                   ((= b 1) (simplify a))
                                   (else (list operation (simplify a) (simplify b)))))
                            (else
                             (list operation (simplify a) (simplify b)))))
                 ((^) (cond ((and (number? a) (number? b)) (expt a b)) ;; use Racket's ability to exponentiate
                            ((number? a)
                             (cond ((= a 1) 1)  ;; ((zero? a) 0) ;; depends on b [b < 0 then undefined]
                                   (else (list operation (simplify a) (simplify b)))))
                            ((number? b)
                             (cond ((zero? b) 1) ;; depends on a [a = 0 then undefined]
                                   ((= b 1) (simplify a))
                                   (else (list operation (simplify a) (simplify b)))))
                            (else (list operation (simplify a) (simplify b)))))))))))

对于某些仅以数字和运算符结尾的示例, 多次申请 simplify 会有帮助。例如。 (simplify (simplify (simplify '(+ (+ (* 2 1) (* x 0)) 0)))) returns 2.

较短解的解释

开始时,if 子句测试表达式是否为列表(not (list? expr))。如果不是,则表示表达式是数字或变量符号。所以我们 return 他们 (expr)。 否则,我们知道 expr 是由以下形式的列表组成的 (<operator - one of */-+^> <first-operand - called a here> <second-operand 'b'>)。 我们通过 cond 子句测试列表的长度。 如果长度为1(= 1 (length expr)),表示设置了一些多余的括号,如((+ 1 2))(1),所以我们return(car expr)让表达式得到去掉多余的括号。 如果列表的长度是 2,那么我们可以处理 (+ 3) => 3 这样的情况 或这种多余的加号。或更复杂的表达式 (+ (* 3 4))。因此,在所有情况下,我们都会对结果递归调用 simplify

如果表达式的长度为 3,我们将解析操作,第一个操作数 a 和第二个操作数 b。那么根据算子的不同,我们有不同的化简规则可以遵循。 然而,这些说明共享所有相似的模式。

;; each of the case branches has the pattern:
((<operator>) (cond ((and (number? a) (number? b)) (<operator> a b))
                    ((number? a)
                    ...)
                    ((number? b)
                    ...)
                    (else
                      (list operation (simplify a) (simplify b)))))

带有条件 (and (number? a) (number? b)) 的第一个子句涵盖了两个操作数都是数字的情况。这种情况很容易处理,因为 Racket "knows" 已经可以处理基本的数学运算。因此,您只需计算该值并将其 return 作为简化(因此:(<operator> a b) 作为 return 值)。 注意:在 / 的情况下,如果 b 为零,则必须引发 Division by zero 错误。但是如果我们为 b, 赋零,Racket 会自动引发它,所以这里我们让 Racket 也进行错误引发,然后让 return 计算 Racket (/ a b).

第二个子句是a是一个数字的情况。 由于第一个测试肯定失败了,我们现在肯定 ab 至少有一个是组合形式或符号,而不是数字.如果这个条件为真,我们就知道 a 是一个数字,但是 b 必须是组合的。 (如果 b 是一个数字,则第一个条件会给出 true ...)。 作为说明,我们再次使用带有子句的 cond 。 这些子句将 a 的特殊情况处理为数字。 加法 + 和减法 - 都对 0 不变,因此这里对两个分支的第一个检查是 a 是否为零 (zero? a)。在这种情况下,我们只省略运算符和 a=0 和 return b。但是由于 b 肯定是一个组合形式,我们在其上调用 simplify 以递归简化 b (否则,表达式 b 将按原样给出而无需进一步简化)。 如果 a 不为零,我们到达 else 子句,知道 a 是一个非零数字并且 b 必须是组合形式。 因此,我们列出了运算符和 ab 中的每一个。 其实 a 不需要进一步简化,因为我们知道,它是一个数字。您可以删除 a 周围的 simplify 调用,只写 a。 对于*/我们区分3种情况:

  • 如果 a 对于 */ 为零,那么我们知道一切都变为零,所以我们 return 在这一点上 0整个表达式。
  • 然后,我们要求 */ 的不变量,即 1。因此 (= 1 a) 所以我们 return 在这种情况下 (simplify b) 没有 a 和运算符。

对于第三个子句,b是一个数字和a组成的情况,一切都和前面的子句一样——只是把b到处替换为a。只有一个例外:对于 /,如果 b 为零,则必须给出 division by zero 错误。因此,我们在这里通过 (error "Divison by 0, statement undefined!") 引发错误。由于 a 不是一个数字,我们不能让 Racket 在这里计算任何东西,所以我们手动抛出一个错误。

第四个子句 (else) 只有在 ab 本身组合时才会被调用,因为这条路径中的所有先前测试都失败了。 在这种情况下,我们递归调用 (simplify a)(simplify b) 并将这些结果与运算符一起列为列表。因此,对于每个操作数,将应用此处描述的简化过程。

重要: ab 的 let-bindings 也在操作数上调用 simplify。如果我们在这里省略 simplify 调用,简化将在一级后停止。因此,simplify 调用——那些在 let 绑定中的调用——以及那些在 cond 下游分支末尾的调用——对于 "pull" 整个表达式的递归是必要的树 - 正如我们前天看到的那样,当时我忘记了 cond 子句中的 simplify ;) 。所以这两层 simplify 调用就像马达一样将简化过程一直拉到底部 ...

解决方案

(define (simplify expr)
  (if (not (list? expr))
      expr
      (let ((operation (car expr))
            (a (cadr expr))
            (b (caddr expr)))
        (case operation
          ((+) (cond ((and (number? a) (number? b))
                      (cond ((zero? a) b)
                            ((zero? b) a)
                            (else (+ a b))))
                     (else (list operation (simplify a) (simplify b)))))
          ((-) (cond ((and (number? a) (number? b))
                      (cond ((zero? a) (- b))
                            ((zero? b) a)
                            (else (- a b))))
                     (else (list operation (simplify a) (simplify b)))))
          ((*) (cond ((and (number? a) (number? b))
                      (cond ((or (zero? a) (zero? b)) 0)
                            ((= a 1) b)
                            ((= b 1) a)
                            (else (* a b))))
                     ((number? a)
                      (cond ((zero? a) 0)
                            ((= a 1) (simplify b))
                            (else (list operation (simplify a) (simplify b)))))
                     ((number? b)
                      (cond ((zero? b) 0)
                            ((= b 1) (simplify a))
                            (else (list operation (simplify a)(simplify b)))))
                     (else (list operation (simplify a) (simplify b)))))
          ((/) (cond ((and (number? a) (number? b))
                      (cond ((zero? b) (error "Divison by 0, statement undefined!"))
                            ((zero? a) 0)
                            ((= b 1) a)
                            (else (/ a b))))
                     ((number? a)
                      (cond ((zero? a) 0)
                            (else (list operation (simplify a) (simplify b)))))
                     ((number? b)
                      (cond ((zero? b) (error "Divison by 0, statement undefined!"))
                            ((= b 1) (simplify a))
                            (else (list operation (simplify a) (simplify b)))))
                     (else
                      (list operation (simplify a) (simplify b)))))
          ((^) (cond ((and (number? a) (number? b))
                      (cond ((and (zero? a) (zero? b)) (error "A and B are both 0, statement undefined!"))
                            ((zero? a) (if (< b 0)
                                           (error "Exponent undefined for 0 and negative B.")
                                           0))
                            ((zero? b) 1)
                            ((= a 1) 1)
                            ((= b 1) a)
                            (else (expt a b))))
                     ((number? a)
                      (cond ((zero? a) 0) ;; depends on b actually - if b < 0 then undefined
                            ((= a 1) 1)
                            (else (list operation (simplify a) (simplify b)))))
                     ((number? b)
                      (cond ((zero? b) 1) ;; depends on a actually - if a = 0 then undefined
                            ((= b 1) (simplify a))
                            (else (list operation (simplify a) (simplify b)))))
                     (else (list operation (simplify a) (simplify b)))))))))

旧版本

(define (simplify expr)
  (if (not (list? expr))
      expr
      (let ((operation (car expr))
            (a (simplify (cadr expr)))
            (b (simplify (caddr expr))))
        (case operation
          ((+) (cond ((and (number? a) (= a 0)) b)
                     ((and (number? b) (= b 0)) a)
                     ((and (number? b) (number? a)) (+ a b))
                     (else (list operation (simplify a) (simplify b)))))
          ((-) (cond ((and (number? a) (= a 0)) (- b))
                     ((and (number? b) (= b 0)) a)     
                     ((and (number? a) (number? b)) (- a b))
                     (else (list operation (simplify a) (simplify b)))))
          ((*) (cond ((and (number? a) (= a 1)) b)
                     ((and (number? a) (= a 0)) 0)
                     ((and (number? a) (number? b) (= b 1)) a)
                     ((and (number? a) (number? b) (= b 0)) 0)
                     ((and (number? a) (number? b)) (* a b))
                     ((and (number? b) (= b 1)) a)               ;; added by me
                     ((and (number? b) (= b 0)) 0)
                     (else (list operation (simplify a) (simplify b)))))
          ((/) (cond ((and (number? a) (= a 0)) 0)
                     ((and (number? a) (number? b) (= b 1)) a)
                     ((and (number? a) (number? b) (= b 0)) (error "Divison by 0, statement undefined!")) ;; error added
                     ((and (number? a) (number? b)) (/ a b))
                     ((and (number? b) (= b 1)) a)               ;; added by me
                     ((and (number? b) (= b 0)) (error "Divison by 0, statement undefined!")) ;; error added
                     (else (list operation (simplify a) (simplify b)))))
          ((^) (cond ((and (number? a) (= a 1)) 1)
                     ((and (number? a) (number? b) (= a 0) (= b 0)) (error "A and B are both 0, statement undefined!"))
                     ((and (number? a) (number? b) (= b 0)) 1)
                     ((and (number? a) (number? b) (= b 1)) a)
                     ((and (number? a) (number? b)) (expt a b))
                     ((and (number? b) (= b 1)) a)               ;; added by me
                     ((and (number? b) (= b 0)) 1)               ;; corrected to 1 (before: zero)
                     (else (list operation (simplify a) (simplify b)))))))))

这正确地简化了。 我以一种非常低效的方式写了它(随之而来的是 cond)所以 - 我会 "simplify" 答案 :D 。我对我添加的行发表评论。 它简化了 (simplify '(+ (* (^ x 5) 0) (* 3 (* 5 (* (^ x 4) 1)))))'(* 3 (* 5 (^ x 4))) 哪个更好(诀窍是递归地调用 simplifyelse 子句中的每个操作数。 但是,我希望最后有 (* 15 (^ x 4))。为此,我们需要进行更多检查...