我想要一种可以使用表达式计算欧几里得距离并排序IQueryable的方法:
sqrt [(q1-p1)^ 2 +(q2-p2)^ 2 + ... +(qn-pn)^ 2]
这是我想出的方法签名:
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;
}
我不确定如何处理item
和total
(因为它们是Expression<Func<T, double>>
)。我尝试了几种不同的方法,包括使用Expression.Power
和Expression.Add
。我尝试过定义要单独构成的表达式:
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
。
是否有更好的方法来解决这个问题?
要使其与EF或LinqToSQL一起使用,您必须将所有信息作为表达式传递,甚至将P和Q的属性访问器作为传递。这就是为什么我修改了方法声明的原因:
public static class Extension
{
public static IOrderedQueryable<T> EuclideanDistanceOrder<T>(
this IQueryable<T> query,
IEnumerable<Expression<Func<T, double>>> pExpressions,
IEnumerable<Expression<Func<T, double>>> qExpressions)
{
var parameter = Expression.Parameter(typeof(T));
var pBodies = pExpressions
.Select(x => ReplaceParameter(x.Body, parameter))
.ToArray();
var qBodies = qExpressions
.Select(x => ReplaceParameter(x.Body, parameter))
.ToArray();
var distances = pBodies
.Select((x, i) => CreateDistance(x, qBodies[i]))
.ToArray();
var squers = distances
.Select(x => CreateSquerExpression(x))
.ToArray();
var sum = squers.First();
for (int i = 1; i < squers.Count(); i++)
{
sum = Expression.Add(sum, squers[i]);
}
var funcExpression = Expression.Lambda<Func<T, double>>(sum, parameter);
//the sqrt is irrelevant to order of this sequence
return query.OrderBy(funcExpression);
}
private static Expression CreateDistance(Expression p, Expression q)
{
return Expression.Subtract(q, p);
}
private static Expression CreateSquerExpression(Expression x)
{
var method = typeof(Math).GetMethod("Pow", BindingFlags.Static | BindingFlags.Public);
return Expression.Call(method, x, Expression.Constant(2.0));
}
private static Expression ReplaceParameter(Expression expression, ParameterExpression parameter)
{
var unaryExpression = expression as UnaryExpression;
MemberExpression memberExpression;
if (unaryExpression != null)
{
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = expression as MemberExpression;
}
if (memberExpression == null)
throw new NotImplementedException();
if (!(memberExpression.Expression is ParameterExpression) || !(memberExpression.Member is PropertyInfo))
throw new NotImplementedException();
return Expression.Property(parameter, (PropertyInfo)memberExpression.Member);
}
}
同时这样调用:
var list = new[]{ new Item
{
P1 = 0,
Q1 = 0,
P2 = 3,
Q2 = 1,
},
new Item
{
P1 = 0,
Q1 = 0,
P2 = 2,
Q2 = 1,
}
};
var query = list.AsQueryable();
var result = query.EuclideanDistanceOrder(new Expression<Func<Item, double>>[]{
x => x.P1,
x => x.P2
},
new Expression<Func<Item, double>>[]{
x => x.Q1,
x => x.Q2
}).ToArray();
internal class Item
{
public double P1 { get; set; }
public double Q1 { get; set; }
public double P2 { get; set; }
public double Q2 { get; set; }
}
它适用于对象。我只是不确定EF或linqtoSql是否将Math.Power
方法映射到sql。如果不是,则很容易变为乘法。
我还无法测试,但似乎应该可以。末尾没有平方根,但是顺序应该是相同的。
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
方法来执行此操作。我只为某些更常见的Replace
类型实现了Expression
,希望这足以满足您的用法,但是您可能需要为其他Expression
类型实现它。
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));
}
因此,例如,我们的输入表达式是:
p => p.X1 - p.X2
p => p.Y1 - p.Y2
原始实现将构建为:
i => 0 + expressions[0](i) ^ 2 + expressions[1](i) ^ 2
新实现采用原始表达式,并用将传递给最终lambda(p
)的参数替换输入参数(上面的i
),并直接在表达式中使用表达式的主体输出:
i => 0 + (i.X1 - i.X2) ^ 2 + (i.Y1 - i.Y2) ^ 2