號稱面試的題目總是非常有趣的,這裏是又一個例子:
【原題出處】
http://topic.csdn.net/u/20110928/15/B00A34FE-8544-42E2-A771-3C4A888DB85A.html
【問題梗概】
求一個函數的一階導數。
【代碼方案】
- namespace Derivative
- {
- class Program
- {
- // 求一個節點表達的算式的導函數
- static Expression GetDerivative(Expression node)
- {
- if (node.NodeType == ExpressionType.Add
- || node.NodeType == ExpressionType.Subtract)
- { // 該節點在做加減法,套用加減法導數公式
- BinaryExpression binexp = (BinaryExpression)node;
- Expression dleft = GetDerivative(binexp.Left);
- Expression dright = GetDerivative(binexp.Right);
- BinaryExpression resbinexp;
- if (node.NodeType == ExpressionType.Add)
- resbinexp = Expression.Add(dleft, dright);
- else
- resbinexp = Expression.Subtract(dleft, dright);
- return resbinexp;
- }
- else if (node.NodeType == ExpressionType.Multiply)
- { // 該節點在做乘法,套用乘法導數公式
- BinaryExpression binexp = (BinaryExpression)node;
- Expression left = binexp.Left;
- Expression right = binexp.Right;
- Expression dleft = GetDerivative(left);
- Expression dright = GetDerivative(right);
- return Expression.Add(Expression.Multiply(dleft, right),
- Expression.Multiply(left, dright));
- }
- else if (node.NodeType == ExpressionType.Parameter)
- { // 該節點是x本身(葉子節點),故而其導數即常數1
- return Expression.Constant(1.0);
- }
- else if (node.NodeType == ExpressionType.Constant)
- { // 該節點是一個常數(葉子節點),故其導數爲零
- return Expression.Constant(0.0);
- }
- else if (node.NodeType == ExpressionType.Call)
- {
- MethodCallExpression callexp = (MethodCallExpression)node;
- Expression arg0 = callexp.Arguments[0];
- // 一下一元函數求導後均需要乘上自變量的導數
- Expression darg0 = GetDerivative(arg0);
- if (callexp.Method.Name == "Exp")
- {
- // 指數函數的導數還是其本身
- return Expression.Multiply(
- Expression.Call(null, callexp.Method, arg0), darg0);
- }
- else if (callexp.Method.Name == "Sin")
- {
- // 正弦函數的倒數是餘弦函數
- MethodInfo miCos = typeof(Math).GetMethod("Cos",
- BindingFlags.Public | BindingFlags.Static);
- return Expression.Multiply(
- Expression.Call(null, miCos, arg0), darg0);
- }
- else if (callexp.Method.Name == "Cos")
- {
- // 餘弦函數的導數是正弦函數的相反數
- MethodInfo miSin = typeof(Math).GetMethod("Sin",
- BindingFlags.Public | BindingFlags.Static);
- return Expression.Multiply(
- Expression.Negate(Expression.Call(null, miSin, arg0)), darg0);
- }
- }
- throw new NotImplementedException(); // 其餘的尚未實現
- }
- static Func<double, double> GetDerivative(Expression<Func<double, double>> func)
- {
- // 從Lambda表達式中獲得函數體
- Expression resBody = GetDerivative(func.Body);
- // 需要續用Lambda表達式的自變量
- ParameterExpression parX = func.Parameters[0];
- Expression<Func<double, double>> resFunc
- = (Expression<Func<double, double>>)Expression.Lambda(resBody, parX);
- Console.WriteLine("diff function = {0}", resFunc);
- // 編譯成CLR的IL表達的函數
- return resFunc.Compile();
- }
- static double GetDerivative(Expression<Func<double, double>> func, double x)
- {
- Func<double, double> diff = GetDerivative(func);
- return diff(x);
- }
- static void Main(string[] args)
- {
- // 舉例:求出函數f(x) = cos(x*x)+sin(3*x)+exp(2*x)在x=2.0處的導數
- double y = GetDerivative(x => Math.Cos(x*x) + Math.Sin(3*x) + Math.Exp(2*x), 2.0);
- Console.WriteLine("f'(x) = {0}", y);
- }
- }
- }
【實現大意】
用表達式分解並遞歸求導(過程是相當容易的,比想象的還容易)。目前只是實現了一個最簡單的模型。
【優勢】
給出的是解析解,在求導運算方面沒有任何數值解的誤差,輸出運算也是瞬時的,時間複雜度僅和表達式複雜度相關。
【限制】
1. 函數只能以Lambda表達式輸入,只能是能求出解析解的表達式
2. 目前只實現了加減法和乘法
【後續擴展】
1. 實現其他運算符(沒有太大難度,只是比較繁瑣而已)
2. 表達式樹優化(也不太難的,根據情況定),最基本的可以從常數乘法開始……
3. 條件運算符的處理(這個會變得極難極複雜,但一定程度上實現分段函數求導),其他特殊情況(對求導還可以,如果考慮求不定積分問題可能會有很多特殊情況和hardcode)
4. 輸入端向字符串解析過渡;複雜運算符->逐漸向自定義的數據結構過渡?……
...