使用表达式计算欧几里得距离的通用方法 [英] Generic method to calculate Euclidean Distance using expressions

查看:170
本文介绍了使用表达式计算欧几里得距离的通用方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要一种可以使用表达式计算欧几里得距离并排序IQueryable的方法:

I'd like a method that can calculate the Euclidean distance using expressions and order an IQueryable:

sqrt [(q1-p1)^ 2 +(q2-p2)^ 2 + ... +(qn-pn)^ 2]

sqrt[(q1 - p1)^2 + (q2 - p2)^2 + ... + (qn - pn)^2]

这是我想出的方法签名:

This is the method signature I've come up with:

public static IOrderedQueryable<T> EuclideanDistanceOrder<T>(
    this IQueryable<T> query, IEnumerable<Expression<Func<T, double>>> expressions)
    {
        var orderedQuery = query.OrderBy(i => Math.Sqrt(expressions.Aggregate((total, item) => total + Math.Pow(item, 2))));
        return orderedQuery;
    }

我不确定该如何处理itemtotal(因为它们是Expression<Func<T, double>>). 我已经尝试了几种不同的方法,包括使用Expression.PowerExpression.Add.我尝试定义要单独构成的表达式:

I'm not sure what to do with item and total (since they are Expression<Func<T, double>>). I've tried this a few different ways, including using Expression.Power and Expression.Add. I've tried defining the expressions to be composed separately:

Expression<Func<double, double>> power = i => Math.Pow(i, 2);
Expression<Func<List<Expression<Func<T, double>>>, double>> dist = (items) => Math.Sqrt(items.Sum(power));

但是我仍然不知道如何使用power.

But I still don't know what to do with power.

有没有更好的方法来解决这个问题?

Is there a better way to approach this?

推荐答案

我无法对此进行测试,但是看来它应该可以工作.末尾没有平方根,但是顺序应该是相同的.

I haven't been able to test this, but it seems that it ought to work. There's no square-root at the end, but the order should be the same either way.

public static IOrderedQueryable<T> EuclideanDistanceOrder<T>(this IQueryable<T> query, IEnumerable<Expression<Func<T, double>>> expressions)
{
    var parameter = Expression.Parameter(typeof(T), "item");
    var seed = Expression.Lambda<Func<T, double>>(Expression.Constant((double)0), parameter);
    return query.OrderBy(expressions.Aggregate(seed, GetAggregateExpression));
}

private static Expression<Func<T, double>> GetAggregateExpression<T>(Expression<Func<T, double>> sum, Expression<Func<T, double>> item)
{
    var parameter = Expression.Parameter(typeof(T), "item");
    return Expression.Lambda<Func<T, double>>(Expression.Add(Expression.Invoke(sum, parameter), Expression.Power(Expression.Invoke(item, parameter), Expression.Constant((double)2))), parameter);
}

由于不能使用Expression.Invoke(),因此需要内联传递到EuclideanDistanceOrder的表达式的正文.似乎没有任何不错"的方法可以完成此操作,因此我编写了Replace方法来执行此操作.我只为某些更常见的Expression类型实现了Replace,希望这足以满足您的用法,但是您可能需要为其他Expression类型实现它.

Since you can't use Expression.Invoke(), you'll need to inline the bodies of the Expressions passed into EuclideanDistanceOrder. There doesn't seem to be any "nice" way to do this, so I've written a Replace method to do it. I've only implemented Replace for some of the more common Expression types, hopefully this will be enough to cover your usage, but you may need to implement it for other Expression types.

public static IOrderedQueryable<T> EuclideanDistanceOrder<T>(this IQueryable<T> query, IEnumerable<Expression<Func<T, double>>> expressions)
{
    var parameter = Expression.Parameter(typeof(T), "item");
    var seed = Expression.Constant((double)0);
    var agg = expressions.Aggregate((Expression)seed, (s, item) => Expression.Add(s, Expression.Power(Replace(item.Body, item.Parameters[0], parameter), Expression.Constant((double)2))));
    return query.OrderBy(Expression.Lambda<Func<T, double>>(agg, parameter));
}

private static Expression Replace(Expression expression, ParameterExpression original, ParameterExpression replacement)
{
    if (expression is BinaryExpression)
    {
        var binaryExpression = (BinaryExpression)expression;
        return Expression.MakeBinary(expression.NodeType, Replace(binaryExpression.Left, original, replacement), Replace(binaryExpression.Right, original, replacement), binaryExpression.IsLiftedToNull, binaryExpression.Method, binaryExpression.Conversion);
    }
    if (expression is ConditionalExpression)
    {
        var conditionalExpression = (ConditionalExpression)expression;
        return Expression.Condition(Replace(conditionalExpression.Test, original, replacement), Replace(conditionalExpression.IfTrue, original, replacement), Replace(conditionalExpression.IfFalse, original, replacement), conditionalExpression.Type);
    }
    if (expression is ConstantExpression)
    {
        return expression;
    }
    if (expression is MemberExpression)
    {
        var memberExpression = (MemberExpression)expression;
        return Expression.MakeMemberAccess(Replace(memberExpression.Expression, original, replacement), memberExpression.Member);
    }
    if (expression is ParameterExpression)
    {
        var parameterExpression = (ParameterExpression)expression;
        return parameterExpression == original ? replacement : parameterExpression;
    }
    if (expression is UnaryExpression)
    {
        var unaryExpression = (UnaryExpression)expression;
        return Expression.MakeUnary(unaryExpression.NodeType, Replace(unaryExpression.Operand, original, replacement), unaryExpression.Type, unaryExpression.Method);
    }
    throw new Exception(string.Format("Unsupported expression type: {0}", expression.NodeType));
}

因此,例如,我们的输入表达式为:

So if for example, our input expressions are:

p => p.X1 - p.X2
p => p.Y1 - p.Y2

原始实现将构建为:

i => 0 + expressions[0](i) ^ 2 + expressions[1](i) ^ 2

新实现采用原始表达式,并将输入参数(上述的p)替换为将传递给最终lambda(i)的参数,并直接在表达式中使用表达式的主体.输出:

The new implementation takes the original expression, and replaces the input parameter (p in the above) with the parameter that will be passed to the final lambda (i), and uses the body of the expression directly in the output:

i => 0 + (i.X1 - i.X2) ^ 2 + (i.Y1 - i.Y2) ^ 2

这篇关于使用表达式计算欧几里得距离的通用方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆