使用表达式树构建 Func<int, double> 多项式
Building a Func<int, double> Polynomial using Expression Trees
TL;DR
如何使用系数数组构建表达式并将其转换为 Func<int, double>
?有没有比表达式树更好的方法?
我有一个不可变的 Sequence 类型,它是使用 Func<int, double> formula
构造的,用于为序列 A 生成项 An。我开始构建一个助手 class 来构造常用的数学公式一些简单的参数:
public static Sequence CreateLinearSequence (double m, double b)
{ return new Sequence (n => m * n + b); }
我为常数序列、对数和简单多项式(线性、二次、三次和四次)构建了标准方法,但我想使用 params
关键字扩展它以支持任意数量的项。
这是我的方法:
public static Sequence CreatePolynomialSequence (params double[] coeff)
{
Expression<Func<int, double>> e = x => 0;
double pow = 0;
for (int i = coeff.Length - 1; i >= 0; i--)
{
double c = coeff[i];
var p = Expression.Parameter (typeof (int), "x");
e = Expression.Lambda<Func<int, double>> (
Expression.Add (
e,
(Expression<Func<int, double>>)(x => c * Math.Pow (x, pow))
),
p);
pow++;
}
return new Sequence (e.Compile ());
}
你们可能很明显我做错了什么;我搞砸了一点,直到我得到一些我觉得 应该 工作的东西,但它没有。
目标是让序列像这样对数组工作double[] coeff = {a,b,c,d,e,f,g,h}
x => h + gx + fx^2 + ex^3 + dx^4 + cx^5 + bx^6 + ax^7
使用适当的 Math.Pow(x, exponent)
调用。
运行
var s2 = SequenceHelper.CreatePolynomialSequence (new[] { 1d, 2 });
Console.WriteLine ("s2: " + s2);
结果
Unhandled Exception: System.InvalidOperationException: The binary
operator Add is not defined for the types
'System.Func2[System.Int32,System.Double]' and
'System.Func
2[System.Int32,System.Double]'. at
System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow
(ExpressionType binaryType, System.String name,
System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right, Boolean liftToNull)
[0x0004a] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658
at System.Linq.Expressions.Expression.Add
(System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right, System.Reflection.MethodInfo
method) [0x00057] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409
at System.Linq.Expressions.Expression.Add
(System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right) [0x00000] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390
at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[]
coeff) [0x00110] in
/Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in
/Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 [ERROR]
FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: The
binary operator Add is not defined for the types
'System.Func2[System.Int32,System.Double]' and
'System.Func
2[System.Int32,System.Double]'. at
System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow
(ExpressionType binaryType, System.String name,
System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right, Boolean liftToNull)
[0x0004a] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658
at System.Linq.Expressions.Expression.Add
(System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right, System.Reflection.MethodInfo
method) [0x00057] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409
at System.Linq.Expressions.Expression.Add
(System.Linq.Expressions.Expression left,
System.Linq.Expressions.Expression right) [0x00000] in
/private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390
at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[]
coeff) [0x00110] in
/Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in
/Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 The
application was terminated by a signal: SIGHUP
您需要解决三件事:
在 Add
内使用 e.Body
而不是 e
。
对所有内容使用相同的参数对象。这有点棘手:Expression.Parameter(typeof (int), "x");
中的x
、e = x => 0
中的x
和x => c * Math.Pow (x, pow)
中的x
是不同个参数。
在循环内创建 pow
的副本。否则,pow
被捕获并且 pow 的相同(最终)值用于所有系数。
在下面的代码示例中,我通过使用内部表达式的参数调用 新表达式来解决第二个问题。另一种选择是手动构建 x => c * Math.Pow(x, pow)
而不是使用 C# lambda 表达式或统一参数,如 this question.
中所述
static void Main(string[] args)
{
var seq = CreatePolynomialSequence(1, 2, 3);
Console.WriteLine(seq.Invoke(1)); // yields 6 = 1 + 2 + 3
Console.WriteLine(seq.Invoke(2)); // yields 11 = 1*4 + 2*2 + 3
}
public static Func<int, double> CreatePolynomialSequence(params double[] coeff)
{
Expression<Func<int, double>> e = x => 0;
double pow = 0;
for (int i = coeff.Length - 1; i >= 0; i--)
{
var p = e.Parameters[0];
double c = coeff[i];
var _pow = pow; // avoid closing over the outer variable
var next = (Expression<Func<int, double>>)(x => c * Math.Pow(x, _pow));
var nextInvoked = Expression.Invoke(next, p);
e = Expression.Lambda<Func<int, double>>(Expression.Add(e.Body, nextInvoked), p);
pow++;
}
return e.Compile();
}
基于@Heinzi 的回答,这里是您如何使用 CreatePolynomialExpression
手动构建整个表达式树的方法:
public static Expression<Func<int, double>> CreatePolynomialExpression(params double[] coeff)
{
if (coeff.Length == 0)
return x => 0;
double pow = 1;
var x_param = Expression.Parameter(typeof(int), "x");
Expression expression = Expression.Constant(coeff[coeff.Length - 1]);
for (int i = coeff.Length - 2; i >= 0; i--)
{
Expression sub_expression =
Expression.Multiply(
Expression.Constant(coeff[i]),
Expression.Power(
Expression.Convert(x_param, typeof(double)),
Expression.Constant(pow)));
expression = Expression.Add(expression , sub_expression);
pow++;
}
return Expression.Lambda<Func<int, double>>(expression, x_param);
}
另一个构建器 with fiddle,它不使用捕获的变量和 Math.Pow
因此工作速度更快
public static Func<int, double> CreatePolynomialSequence (params double[] coeff)
{
// polynom builder
// double argument
var y = Expression.Variable(typeof(double), "y");
// func result
var res = Expression.Variable(typeof(double), "res");
var expr = Expression.Assign(res, Expression.Constant(coeff[0]));
// build polynom in format: ((a*x+b)*x+c)*x+d <=> a*x^3 + b*x^2 + c*x + d
for (int i = 1; i < coeff.Length; i++)
{
expr = Expression.Add
(
Expression.Multiply(expr, y),
Expression.Constant(coeff[i])
);
}
// function body
var x = Expression.Variable(typeof(int), "x");
var block = Expression.Block
(
new ParameterExpression[]{ y, res }, // local variables
new Expression[]
{
// cast int argument to double
Expression.Assign(y, Expression.Convert(x, typeof(double))),
//compute result
expr
}
);
return Expression.Lambda<Func<int, double>>(block, x).Compile();
}
我对这个问题和所有三个答案感到困惑;如果您打算做的只是将它们编译成委托,为什么还要搞乱表达式树?直接 return 代表!
public static Func<double, double> CreatePolynomialFunction (params double[] coeff)
{
if (coeff == null) throw new ArgumentNullException("coeff");
return x =>
{
double sum = 0.0;
double xPower = 1;
for (int power = 0; power < coeff.Length; power += 1)
{
sum += xPower * coeff[power];
xPower *= x;
}
return sum;
};
}
完成。不需要弄乱表达式树。
(我注意到我假设数组中的第 n 项是第 n 个系数;显然你在数组中向后列出了你的系数。这似乎容易出错,但如果那是你想要的那么它并不困难将此答案修改为 运行 循环 down 从 Length-1 到零。)
TL;DR
如何使用系数数组构建表达式并将其转换为 Func<int, double>
?有没有比表达式树更好的方法?
我有一个不可变的 Sequence 类型,它是使用 Func<int, double> formula
构造的,用于为序列 A 生成项 An。我开始构建一个助手 class 来构造常用的数学公式一些简单的参数:
public static Sequence CreateLinearSequence (double m, double b)
{ return new Sequence (n => m * n + b); }
我为常数序列、对数和简单多项式(线性、二次、三次和四次)构建了标准方法,但我想使用 params
关键字扩展它以支持任意数量的项。
这是我的方法:
public static Sequence CreatePolynomialSequence (params double[] coeff)
{
Expression<Func<int, double>> e = x => 0;
double pow = 0;
for (int i = coeff.Length - 1; i >= 0; i--)
{
double c = coeff[i];
var p = Expression.Parameter (typeof (int), "x");
e = Expression.Lambda<Func<int, double>> (
Expression.Add (
e,
(Expression<Func<int, double>>)(x => c * Math.Pow (x, pow))
),
p);
pow++;
}
return new Sequence (e.Compile ());
}
你们可能很明显我做错了什么;我搞砸了一点,直到我得到一些我觉得 应该 工作的东西,但它没有。
目标是让序列像这样对数组工作double[] coeff = {a,b,c,d,e,f,g,h}
x => h + gx + fx^2 + ex^3 + dx^4 + cx^5 + bx^6 + ax^7
使用适当的 Math.Pow(x, exponent)
调用。
运行
var s2 = SequenceHelper.CreatePolynomialSequence (new[] { 1d, 2 });
Console.WriteLine ("s2: " + s2);
结果
Unhandled Exception: System.InvalidOperationException: The binary operator Add is not defined for the types 'System.Func
2[System.Int32,System.Double]' and 'System.Func
2[System.Int32,System.Double]'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow (ExpressionType binaryType, System.String name, System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, Boolean liftToNull) [0x0004a] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, System.Reflection.MethodInfo method) [0x00057] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right) [0x00000] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390 at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[] coeff) [0x00110] in /Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in /Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 [ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: The binary operator Add is not defined for the types 'System.Func2[System.Int32,System.Double]' and 'System.Func
2[System.Int32,System.Double]'. at System.Linq.Expressions.Expression.GetUserDefinedBinaryOperatorOrThrow (ExpressionType binaryType, System.String name, System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, Boolean liftToNull) [0x0004a] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:658 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right, System.Reflection.MethodInfo method) [0x00057] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1409 at System.Linq.Expressions.Expression.Add (System.Linq.Expressions.Expression left, System.Linq.Expressions.Expression right) [0x00000] in /private/tmp/source-mono-mac-4.2.0-branch/bockbuild-mono-4.2.0-branch/profiles/mono-mac-xamarin/build-root/mono-4.2.1/mcs/class/dlr/Runtime/Microsoft.Scripting.Core/Ast/BinaryExpression.cs:1390 at Sequence.SequenceHelper.CreatePolynomialSequence (System.Double[] coeff) [0x00110] in /Users/Knoble/MonoProjects/Sequences/Sequence/SequenceHelper.cs:88
at Sequence.Test.Main () [0x0001f] in /Users/Knoble/MonoProjects/Sequences/Sequence/Test.cs:53 The application was terminated by a signal: SIGHUP
您需要解决三件事:
在
Add
内使用e.Body
而不是e
。对所有内容使用相同的参数对象。这有点棘手:
Expression.Parameter(typeof (int), "x");
中的x
、e = x => 0
中的x
和x => c * Math.Pow (x, pow)
中的x
是不同个参数。在循环内创建
pow
的副本。否则,pow
被捕获并且 pow 的相同(最终)值用于所有系数。
在下面的代码示例中,我通过使用内部表达式的参数调用 新表达式来解决第二个问题。另一种选择是手动构建 x => c * Math.Pow(x, pow)
而不是使用 C# lambda 表达式或统一参数,如 this question.
static void Main(string[] args)
{
var seq = CreatePolynomialSequence(1, 2, 3);
Console.WriteLine(seq.Invoke(1)); // yields 6 = 1 + 2 + 3
Console.WriteLine(seq.Invoke(2)); // yields 11 = 1*4 + 2*2 + 3
}
public static Func<int, double> CreatePolynomialSequence(params double[] coeff)
{
Expression<Func<int, double>> e = x => 0;
double pow = 0;
for (int i = coeff.Length - 1; i >= 0; i--)
{
var p = e.Parameters[0];
double c = coeff[i];
var _pow = pow; // avoid closing over the outer variable
var next = (Expression<Func<int, double>>)(x => c * Math.Pow(x, _pow));
var nextInvoked = Expression.Invoke(next, p);
e = Expression.Lambda<Func<int, double>>(Expression.Add(e.Body, nextInvoked), p);
pow++;
}
return e.Compile();
}
基于@Heinzi 的回答,这里是您如何使用 CreatePolynomialExpression
手动构建整个表达式树的方法:
public static Expression<Func<int, double>> CreatePolynomialExpression(params double[] coeff)
{
if (coeff.Length == 0)
return x => 0;
double pow = 1;
var x_param = Expression.Parameter(typeof(int), "x");
Expression expression = Expression.Constant(coeff[coeff.Length - 1]);
for (int i = coeff.Length - 2; i >= 0; i--)
{
Expression sub_expression =
Expression.Multiply(
Expression.Constant(coeff[i]),
Expression.Power(
Expression.Convert(x_param, typeof(double)),
Expression.Constant(pow)));
expression = Expression.Add(expression , sub_expression);
pow++;
}
return Expression.Lambda<Func<int, double>>(expression, x_param);
}
另一个构建器 with fiddle,它不使用捕获的变量和 Math.Pow
因此工作速度更快
public static Func<int, double> CreatePolynomialSequence (params double[] coeff)
{
// polynom builder
// double argument
var y = Expression.Variable(typeof(double), "y");
// func result
var res = Expression.Variable(typeof(double), "res");
var expr = Expression.Assign(res, Expression.Constant(coeff[0]));
// build polynom in format: ((a*x+b)*x+c)*x+d <=> a*x^3 + b*x^2 + c*x + d
for (int i = 1; i < coeff.Length; i++)
{
expr = Expression.Add
(
Expression.Multiply(expr, y),
Expression.Constant(coeff[i])
);
}
// function body
var x = Expression.Variable(typeof(int), "x");
var block = Expression.Block
(
new ParameterExpression[]{ y, res }, // local variables
new Expression[]
{
// cast int argument to double
Expression.Assign(y, Expression.Convert(x, typeof(double))),
//compute result
expr
}
);
return Expression.Lambda<Func<int, double>>(block, x).Compile();
}
我对这个问题和所有三个答案感到困惑;如果您打算做的只是将它们编译成委托,为什么还要搞乱表达式树?直接 return 代表!
public static Func<double, double> CreatePolynomialFunction (params double[] coeff)
{
if (coeff == null) throw new ArgumentNullException("coeff");
return x =>
{
double sum = 0.0;
double xPower = 1;
for (int power = 0; power < coeff.Length; power += 1)
{
sum += xPower * coeff[power];
xPower *= x;
}
return sum;
};
}
完成。不需要弄乱表达式树。
(我注意到我假设数组中的第 n 项是第 n 个系数;显然你在数组中向后列出了你的系数。这似乎容易出错,但如果那是你想要的那么它并不困难将此答案修改为 运行 循环 down 从 Length-1 到零。)