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

与世无争的帅哥 提交于 2019-12-08 06:01:36

问题


This is my function for differentiation that works correctly.

#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 ))))                           
))))

and now I also have most of the simplification function working correctly but there is a problem somewhere and i think it is in my power rule simplifier

(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)]))
 ))))

I have run many tests and most pass but there are a few that don't and I can't figure out why.

(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

回答1:


Shorter Solution

By using Racket's intrinsic ability to calculate number (and do the correct error messaging e.g. division by zero error)

#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)))))))))))

For some examples, which end up in numbers and operators only, it helps to apply simplify several times. E.g. (simplify (simplify (simplify '(+ (+ (* 2 1) (* x 0)) 0)))) returns 2.

Explanation of the shorter solution

At the beginning, the if-clause tests, whether the expression is a list or not (not (list? expr)). If not, it means the expression is either a number or a variable symbol. So we return them (expr). Otherwise, we know that expr is composed - a list - of the form (<operator - one of */-+^> <first-operand - called a here> <second-operand 'b'>). We test for the length of the lists by the cond clauses. If the length is 1 (= 1 (length expr)), it means that some superfluous parentheses were set, like ((+ 1 2)) or (1), so we return (car expr) to let the expression get rid of superfluous parantheses. If the length of the list is 2, then we can handle cases like (+ 3) => 3 or such kind of superfluous plus sign. or more complex expressions (+ (* 3 4)). So in all cases we recursively call simplify on the results.

If the expression has the length 3, we parse the operation, the first operand a and the second operand b. Then according to the operator, we have different simplification rules to follow. However the instructions share all a similar pattern.

;; 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)))))

The first clause with the condition (and (number? a) (number? b)) covers the case that both operands are numbers. This case is simple to handle, because Racket "knows" already how to handle the basic mathematical operations. So you just calculate the value and return it as the simplification (thus: (<operator> a b) as return value). Note: In case of /, if b is zero, then a Division by zero error has to be raised. But Racket raises it automatically if we give zero for b, so here we just let Racket do also the error raising and just let return and calculate Racket (/ a b).

The second clause is the case that a is a number. Since the first test must have failed, we now know for sure that at least one of a or b if not both are composed forms or symbols and not numbers. If this condition is true, we know that a is a number, but b must be composed. (If b would be a number, the first condition would have given true ...). As instruction, we have again a cond with clauses. The clauses handle the special cases of a as a number. Addition + and Subtraction - both are invariant to 0, thus the first check for both branches here is, whether a is zero (zero? a). In this case, we leave out operator and a=0 and return only b. But since b is for sure a composed form, we call simplify on it, to recursively simplify b (otherwise, the expression b would be given as it is without further simplification). If a is not zero, we arrive to the else clause, knowing that a is a non-zero number and b must be a composed form. Thus, we list the operator and a and b each of them simpified. Actually a doesn't need to be simplified further, since we know, it is a number. You could remove the simplify call around a and just write a. For * and / we distinguish 3 cases:

  • if a is zero for * or /, then we know that everything becomes zero, so we return at this point 0 for the entire expression.
  • then, we ask for the invariant for * or /, which is 1. Thus (= 1 a) So we return in this case (simplify b) without a and the operator.

For the third clause, the case that b is a number and a composed, everything is the same like in the previous clause - just replacing everywhere b for a. And only one exception: for / if b is zero, then a division by zero error has to be given. Thus we raise here an error by (error "Divison by 0, statement undefined!"). Since a is not a number, we cannot let Racket calculate here out anything, so we raise manually an error.

The fourth clause (else) will be invoked only if a and b, are themselves composed, since all previous tests in this path failed. In this case, we recursively call (simplify a) and (simplify b) and list those results together with the operator as a list. So for each operand the here described simplification process will be applied.

Important: The let-bindings for a and b call also simplify on the operands. If we leave out the simplify calls here, the simplification stops after one level. So both simplify calls - those in the let bindings - and those at the end of the cond-branches downstream - are necessary to "pull" the recursion down through the entire expression tree - as we have seen day before yesterday, when I forgot the simplify in the cond clauses ;) . So these two layers of simplify calls act like motors to pull the simplification process all the way down to the bottom ...

Solution

(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)))))))))

Old version

(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)))))))))

This simplifies correctly. I wrote it in a quite inefficient way (with consequent cond) so - I will "simplify" the answer :D . I put comment which lines I added. It simplifies (simplify '(+ (* (^ x 5) 0) (* 3 (* 5 (* (^ x 4) 1))))) to '(* 3 (* 5 (^ x 4))) which is better (the trick is to recursively to call simplify on each of the operands in the else clauses. However, I'd like to have (* 15 (^ x 4)) at the end. For this, we need some more checks ...



来源:https://stackoverflow.com/questions/52880911/how-to-fix-my-simplification-function-for-power-rule-to-make-results-easier-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!