0

式を使用してユークリッド距離を計算し、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;
    }

itemandをどうすればよいかわかりません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

これにアプローチするより良い方法はありますか?

4

2 に答える 2

1

EF または LinqToSQL で動作させるには、P および Q のプロパティ アクセサーであっても、すべての情報を Expressions として渡す必要があります。そのため、メソッド宣言を変更しました。

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 にマップするかどうかだけはわかりません。そうでない場合は、乗算に変更するのは簡単です。

于 2012-07-20T07:12:04.610 に答える
1

これをテストすることはできませんでしたが、うまくいくはずです。最後に平方根はありませんが、順序はどちらでも同じでなければなりません。

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

新しい実装は元の式を取り、入力パラメーター (p上記) を最終ラムダに渡されるパラメーター ( ) に置き換え、i式の本体を出力で直接使用します。

i => 0 + (i.X1 - i.X2) ^ 2 + (i.Y1 - i.Y2) ^ 2
于 2012-07-20T06:35:36.077 に答える