How return vectors' scalar product using Expression trees

193 Views Asked by At

I try to return vectors' scalar product using expression trees, but I can not loop into arrays, can u help me? The task description and code please see below:

public class CodeGeneration
{
    public static Func<T[], T[], T> GetVectorMultiplyFunction<T>() where T : struct
    {
        // TODO : Implement GetVectorMultiplyFunction<T>.
            
        // Input
        ParameterExpression arr1 = Expression.Parameter(typeof(T[]), "first");
        ParameterExpression arr2 = Expression.Parameter(typeof(T[]), "second");
        ParameterExpression result = Expression.Parameter(typeof(T), "result");
            
        // Creating a label to jump to from a loop. 
        LabelTarget label = Expression.Label(typeof(int));
            
        // Creating a method body
        BlockExpression block = Expression.Block(
                            // Adding a local variable.  
                            new[] { result },
                            // Assigning a constant to a local variable: result = 1  
                            Expression.Assign(result, Expression.Constant(0)),
                                // Adding a loop.  
                                Expression.Loop(
                                   // Adding a conditional block into the loop.  
                                   Expression.IfThenElse(
    
    // ..........................               
    // I can not write the iteration for input arrays like the method below:
    // ..........................
            
                                       Expression.Break(label, result)
                                   ),
                               // Label to jump to.  
                               label
                            )
                        );
} 


// solution simple    
public static int MultiplyVectors(int[] first, int[] second)
{
    int result = 0;

    for (int i = 0; i < first.Length; i++) 
    {
        result += first[i] * second[i];
    }

    return result;
}
1

There are 1 best solutions below

3
BurnsBA On

If all you need is an expression, then for "single" lambda statements you can just declare an expression:

Expression<Func<double[], double[], double>> lambda = (arr1, arr2) => arr1.Zip(arr2).Sum(x => x.First * x.Second);

But it sounds like this is homework problem, so at least you can use the above for testing.


You need to declare a loop variable, and increment it in the loop. ~ How to create a loop expression tree

You will also need Expression.ArrayAccess to retrieve value of an array.

You also need to get the array length. Probably you can simply use Expression.ArrayLength, but because you are using generics, it might be safer to retrieve the Length property ~ Expression getting length of an array

Putting this all together:

public static Func<T[], T[], T> BuildExpression<T>()
{
    ParameterExpression result = Expression.Parameter(typeof(T), "result");
    ParameterExpression i = Expression.Parameter(typeof(int), "i");
    ParameterExpression firstLength = Expression.Parameter(typeof(int), "firstLength");

    ParameterExpression firstArrayExpr = Expression.Parameter(typeof(T[]), "FirstArray");
    ParameterExpression secondArrayExpr = Expression.Parameter(typeof(T[]), "SecondArray");

    // How to get length of an array
    // https://stackoverflow.com/questions/50455090/expression-getting-length-of-an-array
    MethodInfo arrayLengthGetter = typeof(Array).GetProperty("Length").GetGetMethod();

    // Label target needs to be same as return type
    LabelTarget label = Expression.Label(typeof(T));

    /////

    // Following is expression "method", double slash comments (//) are code the expression implements,
    // double double slash comments (////) are higher level comments.

    var methodBody = Expression.Block(
        //// local variables
        new[] { result, i, firstLength },

        // result = default(T);
        Expression.Assign(result, Expression.Constant(default(T))),

        // firstLength = FirstArray.Length;
        Expression.Assign(firstLength, Expression.Call(firstArrayExpr, arrayLengthGetter)),

        // while (true) {
        Expression.Loop(
            Expression.Block(
                Expression.IfThenElse(
                    // if (i < firstLength) {
                    Expression.LessThan(i, firstLength),
                    //    result += FirstArray[i] * SecondArray[i];
                    Expression.AddAssign(result, Expression.Multiply(

                        //// how to access array by index
                        //// https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression.arrayaccess?view=net-6.0
                        //// https://stackoverflow.com/questions/14973813/arrayaccess-vs-arrayindex-in-expression-tree
                        Expression.ArrayAccess(firstArrayExpr, i),
                        Expression.ArrayAccess(secondArrayExpr, i)
                        )),
                    // } else { 
                    //     goto label;
                    // }
                    Expression.Break(label, result)
                    ),
                // i += 1;
                Expression.AddAssign(i, Expression.Constant(1))
            ),
        // } //// end while
        //// `while` break target:
            label
    ));

    var func = Expression.Lambda<Func<T[], T[], T>>(methodBody, firstArrayExpr, secondArrayExpr);

    return func.Compile();
}

With the MultuplyVectors declared above, you can test with (note I changed the types to double to demo generic T):

static void Main(string[] args)
{
    var list1 = new List<double>() { 2, 4, 7, 10, 11, 12, 13, 14 };
    var list2 = new List<double>() { 3, 6, 9, 10, 11, 12, 13, 14 };

    Expression<Func<double[], double[], double>> lambda = (arr1, arr2) => arr1.Zip(arr2).Sum(x => x.First * x.Second);
    var f = lambda.Compile();

    var expr = BuildExpression<double>();

    var fsum = f(list1.ToArray(), list2.ToArray());
    var calcsum = MultuplyVectors(list1.ToArray(), list2.ToArray());
    var exprsum = expr.Invoke(list1.ToArray(), list2.ToArray());

    Console.WriteLine($"fsum: {fsum}");
    Console.WriteLine($"calcsum: {calcsum}");
    Console.WriteLine($"exprsum: {exprsum}");
}

console output:

fsum: 823
calcsum: 823
exprsum: 823

Note: in visual studio debugger, there is a debug (visual studio runtime) only property on Expression called DebugView which will explain how the expression is defined. For example

.Lambda #Lambda1<System.Func`3[System.Double[],System.Double[],System.Double]>(
    System.Double[] $FirstArray,
    System.Double[] $SecondArray) {
    .Block(
        System.Double $result,
        System.Int32 $i,
        System.Int32 $firstLength) {
        $result = 0D;
        $firstLength = .Call $FirstArray.get_Length();
        .Loop  {
            .Block() {
                .If ($i < $firstLength) {
                    $result += $FirstArray[$i] * $SecondArray[$i]
                } .Else {
                    .Break #Label1 { $result }
                };
                $i += 1
            }
        }
        .LabelTarget #Label1:
    }
}