(ns e2-56-57-58
  (:use clojure.test))
(defn variable? [x]
  (symbol? x))
(defn same-variable? [x y]
  (and (variable? x) (variable? y) (= x y)))
(defn make-sum [a b]
  ['+ a b])
(defn sum? [a]
  (= (first a) '+))
(defn addend [a]
  (second a))
(defn augend [a]
  (second (next a)))
(defn make-product [a b]
  ['* a b])
(defn product? [a]
  (= (first a) '*))
(defn multiplier [a]
  (second a))
(defn multiplicand [a]
  (second (next a)))
(defn is-number? [x]
  (and (not (variable? x)) (number? x)))
(defn deriv [exp v]
  (cond (is-number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        (sum? exp) (make-sum (deriv (addend exp) v)
                             (deriv (augend exp) v))
        (product? exp) (make-sum (make-product (multiplier exp)
                                               (deriv (multiplicand exp) v))
                                 (make-product (multiplicand exp)
                                               (deriv (multiplier exp) v)))
        :else nil))
(deftest test-deriv-as-in-book
  (is (= (deriv '(+ x 3) 'x) ['+ 1 0]))
  (is (= (deriv '(* x y) 'x) ['+ ['* 'x 0] ['* 'y 1]])))
(defn exponentiation? [a]
  (= (first a) '**))
(defn base [a]
  (second a))
(defn exponent [a]
  (second (next a)))
(defn make-exponentiation [base exp]
  (cond (= exp 0) 1
        (= exp 1) base
        :else ['** base exp]))
(defn make-sum-opt [a b]
  (cond (and (is-number? a) (= a 0)) b
        (and (is-number? b) (= b 0)) a
        (and (is-number? a) (is-number? b)) (+ a b)
        :else ['+ a b]))
(defn deriv-exp [exp v]
  (cond (is-number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        (sum? exp) (make-sum-opt (deriv-exp (addend exp) v)
                                 (deriv-exp (augend exp) v))
        (product? exp) (make-sum-opt (make-product (multiplier exp)
                                                   (deriv-exp (multiplicand exp) v))
                                     (make-product (multiplicand exp)
                                                   (deriv-exp (multiplier exp) v)))
        (exponentiation? exp) (make-product
                                (make-product (exponent exp)
                                              (make-exponentiation (base exp)
                                                                   (make-sum-opt (exponent exp) -1)))
                                (deriv-exp (base exp) v))
        :else nil))
(deftest test-deriv-modified
  (is (= (deriv-exp '(+ x 3) 'x) 1))
  (is (= (deriv-exp '(* x y) 'x) ['+ ['* 'x 0] ['* 'y 1]]))
  (is (= (deriv-exp '(** x 3) 'x) ['* ['* 3 ['** 'x 2]] 1])))
(defn make-product-2-57 [multiplier & m]
  (concat (list '* multiplier) m))
(defn multiplier-2-57 [a]
  (second a))
(defn multiplicand-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-product-2-57 (first m) (rest m))
          :else (first m))))
(defn make-sum-2-57 [addend & a]
  (concat (list '+ addend) a))
(defn addend-2-57 [a]
  (second a))
(defn augend-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-sum-2-57 (first m) (rest m))
          :else (first m))))
(defn deriv-2-57 [exp v]
  (cond (is-number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        (sum? exp) (make-sum-2-57 (deriv-2-57 (addend-2-57 exp) v)
                                  (deriv-2-57 (augend-2-57 exp) v))
        (product? exp) (make-sum-2-57 (make-product-2-57 (multiplier-2-57 exp)
                                                         (deriv-2-57 (multiplicand-2-57 exp) v))
                                      (make-product-2-57 (multiplicand-2-57 exp)
                                                         (deriv-2-57 (multiplier-2-57 exp) v)))
        :else nil))
(defn make-product-2-57 [multiplier & m]
  (concat (list '* multiplier) m))
(defn multiplier-2-57 [a]
  (second a))
(defn multiplicand-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-product-2-57 (first m) (rest m))
          :else (first m))))
(defn make-sum-2-57 [addend & a]
  (concat (list '+ addend) a))
(defn addend-2-57 [a]
  (second a))
(defn augend-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-sum-2-57 (first m) (rest m))
          :else (first m))))
(defn make-product-2-57 [multiplier & m]
  (concat (list '* multiplier) m))
(defn multiplier-2-57 [a]
  (second a))
(defn multiplicand-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-product-2-57 (first m) (rest m))
          :else (first m))))
(defn make-sum-2-57 [addend & a]
  (concat (list '+ addend) a))
(defn addend-2-57 [a]
  (second a))
(defn augend-2-57 [a]
  (let [m (next (next a))]
    (cond (> (count m) 1) (make-sum-2-57 (first m) (rest m))
          :else (first m))))
(defn product-2-58a? [a]
  (= (second a) '*))
(defn make-product-2-58a [a b]
  (list a '* b))
(defn multiplier-2-58a [a]
  (first a))
(defn multiplicand-2-58a [a]
  (second (next a)))
(defn sum-2-58a? [a]
  (= (second a) '+))
(defn make-sum-2-58a [a b]
  (list a '+ b))
(defn addend-2-58a [a]
  (first a))
(defn augend-2-58a [a]
  (second (next a)))
(defn deriv-2-58a [exp v]
  (cond (is-number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        (sum-2-58a? exp) (make-sum-2-58a (deriv-2-58a (addend-2-58a exp) v)
                                  (deriv-2-58a (augend-2-58a exp) v))
        (product-2-58a? exp) (make-sum-2-58a (make-product-2-58a (multiplier-2-58a exp)
                                                         (deriv-2-58a (multiplicand-2-58a exp) v))
                                      (make-product-2-58a (multiplicand-2-58a exp)
                                                         (deriv-2-58a (multiplier-2-58a exp) v)))
        :else nil))
(deftest test-258a
  (is (= (deriv-2-58a '((x * y) * (x + 3)) 'x) '(((x * y) * (1 + 0)) + ((x + 3) * ((x * 0) + (y * 1))))))
  (is (= (deriv-2-58a '(x + (3 * (x + (y + 2)))) 'x) '(1 + ((3 * (1 + (0 + 0))) + ((x + (y + 2)) * 0))))))
(defn supported-op? [x]
  (or (= x '+) (= x '*)))
(defn product-2-58b? [a]
  (reduce #(and % %2) true
  (map (fn [x]
         (cond (= x '+) false
               (= x '*) true))
    (filter #(supported-op? %) a))))
(defn make-product-2-58b [a b]
  (list a '* b))
(defn first-part [a sym]
  (let [m (take-while #(not (= % sym)) a)]
    (cond (> (count m) 1) m
          :else (first m))))
(defn second-part [a sym]
  (let [m (drop 1 (drop-while #(not (= % sym)) a))]
    (cond (> (count m) 1) m
          :else (first m))))
(defn multiplier-2-58b [a]
  (first-part a '*))
(defn multiplicand-2-58b [a]
  (second-part a '*))
(defn sum-2-58b? [a]
  (reduce #(or % %2) false
  (map #(= % '+)
       (filter #(supported-op? %) a))))
(defn make-sum-2-58b [a b]
  (list a '+ b))
(defn addend-2-58b [a]
  (first-part a '+))
(defn augend-2-58b [a]
  (second-part a '+))
(defn deriv-2-58b [exp v]
  (cond (is-number? exp) 0
        (variable? exp) (if (same-variable? exp v) 1 0)
        (sum-2-58b? exp) (make-sum-2-58b (deriv-2-58b (addend-2-58b exp) v)
                                  (deriv-2-58b (augend-2-58b exp) v))
        (product-2-58b? exp) (make-sum-2-58b (make-product-2-58b (multiplier-2-58b exp)
                                                         (deriv-2-58b (multiplicand-2-58b exp) v))
                                      (make-product-2-58b (multiplicand-2-58b exp)
                                                         (deriv-2-58b (multiplier-2-58b exp) v)))
        :else nil))
(deftest test-258b
  (is (= (deriv-2-58b '((x * y) * (x + 3)) 'x) '(((x * y) * (1 + 0)) + ((x + 3) * ((x * 0) + (y * 1))))))
  (is (= (deriv-2-58b '(x + 3 * (x + y + 2)) 'x) '(1 + ((3 * (1 + (0 + 0))) + ((x + y + 2) * 0))))))