我一直在读一本关于线性代数数学和编程微积分的书,有一件事让我印象深刻,那就是代码的冗长。 首先,我知道这样是什么,性能比使用 for 循环要好得多,但是,这使得代码非常难以阅读。
有没有办法使用 for 循环来创建详细函数,这样代码更易于维护?
这是我所说的一个非常简单的例子:
public int Sum(int[] a, int[] b, int size)
{
var sum = 0;
for(var index = 0; index < size; index++)
{
sum += a[index] + b[index];
}
return sum;
}
现在假设size
是 3。 是否可以从方法Sum
生成如下所示的单个表达式:
public int Sum3(int[] a, int[] b)
{
return a[0] + b [0] + a[1] + b[1] + a[2] + b[2];
}
因此,实际上,Sum
的方法不是执行求和,而是用于创建可以求和的delegate
。 它最终看起来像Func<int[], int[], int> Sum3
.
目标是使用这种方法进行点积、行列式和矩阵乘法等操作。
更新:
我不希望在一行代码中对两个数组求和。这只是一个例子。我希望能够在不实际循环的情况下进行任何矢量或矩阵操作。
(这只是部分答案;它不涉及读取Sum
metbod的IL。
解析原始函数(在本例中Sum
(后,您可以按如下方式构造相应的Sum3
函数:
// using static System.Linq.Expressions.Expression;
var a = Parameter(typeof(int[]));
var b = Parameter(typeof(int[]));
var expr1 = Lambda(
Add(
Add(
Add(
Add(
Add(
ArrayIndex(a, Constant(0)),
ArrayIndex(b, Constant(0))
),
ArrayIndex(a,Constant(1))
),
ArrayIndex(b, Constant(1))
),
ArrayIndex(a, Constant(2))
),
ArrayIndex(b, Constant(2))
),
a,
b
);
var fn = expr1.Compile();
fn.DynamicInvoke(new[] { 1, 2, 3 }, new[] { 4, 5, 6 });
当然,您可能希望根据 IL 中的内容自定义逻辑。
另请注意,这很可能性能较低,因为编译表达式树所需的时间通常至少比简单的循环大一个数量级。除非你可以缓存各种size
值的编译委托,并且用法的数量证明了这一点。
注意:这些代码大部分来自我编写的 ExpressionTreeToString 库,它可以生成创建给定表达式树所需的工厂方法调用。
// using ExpressionTreeToString;
Expression<Func<int[], int[], int>> expr = (a, b) => a[0] + b[0] + a[1] + b[1] + a[2] + b[2];
Console.WriteLine(expr.ToString("Factory methods"));
也许这种方式适用于您:
int[] arr1 = { 1, 2, 3, 4, 5 };
int[] arr2 = { 1, 2, 3, 4, 5 };
int index = 3;
var overallSum = arr1.Take(index).Sum() + arr2.Take(index).Sum();
如果您真的不想在最终运行时中迭代数据,我认为在编译时不可能完成此操作,除非您始终知道数组的确切长度(或者您为sum
进行Int32.MaxValue
覆盖(。您必须在运行时使用 C# 代码编写一些脚本,然后执行它。