c# - 穷举搜索/生成表达式树的每个组合

标签 c# tree expression expression-trees visitor-pattern

我正在使用一个基本的表达式树优化器来构建查询计划。解析树时,我可以决定如何“最好”地构建它,具体取决于我可以分配给每个操作的权重。

如果我有一个简单的树,有 2 个关于如何执行 Action 的选择,我希望能够生成树的两个变体,然后可以比较每个变体的权重以查看最有效的.

例如,下面的代码将允许我构建表达式树连接操作的两种变体:一种带有 MergeJoinExpression,另一种带有 NestedLoopJoinExpression

class Customer
{
        public int Id { get; set; }
}
class Orders
{
        public int Id { get; set; }
        public int CustomerId { get; set; }
}

class MergeJoinExpresion : JoinExpression
{
}

class NestLoopJoinExpresion : JoinExpression
{
}

class Visitor : ExpressionVisitor
{
    public List<Expression> GetPlans(Expression expr)
    {
        // ???
    }

    override VisitJoin(JoinExpression join)
    {
        // For this join, I can return the following (trite example)
        // return MergeJoinExpresion
        // return NestLoopJoinExpresion

        return base.VisitJoin(join);
    }
}

我如何构建一个方法来生成树的每个变体并将它们返回给我?

class Program
{
        static void Main(string[] args)
        {
             var query = from c in customers
                        join o in orders on c.Id equals o.CustomerId
                        select new
                        {
                            CustomerId = c.Id,
                            OrderId = o.Id
                        };


            var plans = new Visitor().GetPlans(query);
        }
}

谁能告诉我如何修改 VisitorGetPlans 方法来生成这些变体?

编辑 - 类似于:

class Visitor : ExpressionVisitor
{
    private List<Expression> exprs = new List<Expression>();

    public List<Expression> GetPlans(Expression expr)
    {
        Visit(expr);    
        return exprs;
    }

    override VisitJoin(JoinExpression join)
    {
        // For this join, I can return the following (trite example)
        // return MergeJoinExpresion
        // return NestLoopJoinExpresion      
        var choices = new Expression[] { MergeJoinExpresion.Create(join), NestLoopJoinExpresion.Create(join) };

        foreach(var choice in choices)
        {
             var cloned = Cloner.Clone(choice);
             var newTree = base.VisitJoin(cloned);
             exprs.Add(newTree);
        }

        return base.VisitJoin(join);
    }
}

最佳答案

因此,首先我们将创建一个访问者,它只会帮助我们从 Expression 中提取 JoinExpression 对象的列表:

internal class FindJoinsVisitor : ExpressionVisitor
{
    private List<JoinExpression> expressions = new List<JoinExpression>();
    protected override Expression VisitJoin(JoinExpression join)
    {
        expressions.Add(join);
        return base.VisitJoin(join);
    }
    public IEnumerable<JoinExpression> JoinExpressions
    {
        get
        {
            return expressions;
        }
    }
}
public static IEnumerable<JoinExpression> FindJoins(
    this Expression expression)
{
    var visitor = new FindJoinsVisitor();
    visitor.Visit(expression);
    return visitor.JoinExpressions;
}

接下来我们将使用以下方法,取自this blog post , 得到一系列序列的笛卡尔积:

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
    this IEnumerable<IEnumerable<T>> sequences) 
{ 
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
    return sequences.Aggregate( 
        emptyProduct, 
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item})); 
}

接下来我们将创建一个访问者,它接受一系列表达式对,并将该对中第一个表达式的所有实例替换为第二个表达式:

internal class ReplaceVisitor : ExpressionVisitor
{
    private readonly Dictionary<Expression, Expression> lookup;
    public ReplaceVisitor(Dictionary<Expression, Expression> pairsToReplace)
    {
        lookup = pairsToReplace;
    }
    public override Expression Visit(Expression node)
    {
        if(lookup.ContainsKey(node))
            return base.Visit(lookup[node]);
        else
            return base.Visit(node);
    }
}

public static Expression ReplaceAll(this Expression expression,
    Dictionary<Expression, Expression> pairsToReplace)
{
    return new ReplaceVisitor(pairsToReplace).Visit(expression);
}

public static Expression ReplaceAll(this Expression expression,
    IEnumerable<Tuple<Expression, Expression>> pairsToReplace)
{
    var lookup = pairsToReplace.ToDictionary(pair => pair.Item1, pair => pair.Item2);
    return new ReplaceVisitor(lookup).Visit(expression);
}

最后,我们通过查找表达式中的所有连接表达式将所有内容放在一起,将它们投影到一个对序列中,其中 JoinExpression 是对中的第一个项目,第二个是每个可能的重置值。从那里我们可以取它的笛卡尔积以获得成对的表达式替换的所有组合。最后,我们可以将每个替换组合转换到表达式中,该表达式实际上替换了原始表达式中的所有这些对:

public static IEnumerable<Expression> AllJoinCombinations(Expression expression)
{
    var combinations = expression.FindJoins()
        .Select(join => new Tuple<Expression, Expression>[]
        {
            Tuple.Create<Expression, Expression>(join, new NestLoopJoinExpresion(join)), 
            Tuple.Create<Expression, Expression>(join, new MergeJoinExpresion(join)),
        })
        .CartesianProduct();

    return combinations.Select(combination => expression.ReplaceAll(combination));
}

关于c# - 穷举搜索/生成表达式树的每个组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33500649/

相关文章:

c - K叉树(排序词)

reporting-services - 十二个月前作为 SSRS 中的参数

asp.net-mvc - 向表达式添加断行

python - 将 nltk 树转换为 JSON 表示

c# - 为 List.Any(v => v.Contains(Book.Title.ToString())) 生成表达式树

c# - 使用 MouseHover 使 PictureBox 可见/不可见

javascript - Ajax 函数在重定向后不保存滚动位置

c# - 在 DataSet 中使用函数时为 "undefined function call"

c# - 如何在没有数据库的情况下在 ASP.NET Core 中创建一个简单的登录并授权 Controller 访问

java - InOrder 遍历进入无限循环并仅打印第一个节点