【问题标题】:Best approach to parse the expression - Multiple variables [duplicate]解析表达式的最佳方法 - 多个变量
【发布时间】:2020-04-12 07:12:05
【问题描述】:

我有一种情况,我需要解析多个 n 个相关字段(不想评估):

    string exp1 = "10";
    string exp2 = "20";
    string exp3= "exp1 + exp2 + 30";
    string exp4 = "exp5 - exp3"; 
    string exp5 = "exp3 / 10";

    Dictionary<string,string> expressions = new Dictionary<string,string>();
    expressions.Add("exp1", exp1);
    expressions.Add("exp2", exp2);
    expressions.Add("exp3", exp3);
    expressions.Add("exp4", exp3);
    expressions.Add("exp5", exp5);

现在我们要遍历所有表达式字段并用实际值解析表达式(也应该应用 Bodmas)。所以,解析后,我们希望下面作为输出:

exp1 = "10";
exp2 = "20";
exp3= "10 + 20 + 30";
exp4 = "((10 + 20 + 30 )/10) - (10+ 20 + 30)";
exp5 = "(10 + 29 + 30)/ 10";

我应该在这里使用什么数据结构来使用通用方式对其进行解析?会是二叉树、图表、表达式树吗?

【问题讨论】:

  • 我会使用递归并使用字典而不是列表。
  • @Program :我不想评估,我只需要用实际值解析表达式。表达式之间有n级关系。
  • @MarkCiliaVincenti:是的,递归会起作用。另外,我已经更新了使用字典的问题。还有其他通用方法吗?
  • @MarkCiliaVincenti 递归在“exp4”处会出现问题,因为其中包含尚未评估的“exp5”。我的意思是当你点击“exp4”时你会做什么? 编辑如果您可以更改顺序以便“exp5”解析“exp4”,您可以按顺序解析它们
  • @styx:我无法更改顺序,因为我不知道在哪个表达式中我们可以有其他表达式。因为可以通过 UI 随时更改表达式。所以我们不能为订购做任何事情。这就是我问这个问题的原因和原因。

标签: c#


【解决方案1】:

守则

using System.Collections.Generic;
using Microsoft.VisualBasic;    //the code was written in VB and converted. make appropriate changes if you don't want to use this namespace

class Exp
{
    private Dictionary<string, string> AllExpressions = new Dictionary<string, string>();

    public void Add(string name, string value)
    {
        AllExpressions.Add(name, value);
    }

    public string ValueOf(string name)
    {
        var parts = Strings.Split(AllExpressions[name]);
        for (int i = 0; i <= parts.Length - 1; i++)
        {
            if (AllExpressions.ContainsKey(parts[i]))
            {
                var partVal = ValueOf(parts[i]);
                parts[i] =  Information.IsNumeric(partVal) ? partVal : $"({partVal})";
            }
        }
        return Strings.Join(parts);
    }
}

用法

Exp myExp = new Exp();
myExp.Add("exp1", "10");
myExp.Add("exp2", "20");
myExp.Add("exp3", "exp1 + exp2 + 30");
myExp.Add("exp4", "exp5 - exp3");
myExp.Add("exp5", "exp3 / 10");

// Test to see if we can get correct values 
Console.WriteLine(myExp.ValueOf("exp1"));
Console.WriteLine(myExp.ValueOf("exp2"));
Console.WriteLine(myExp.ValueOf("exp3"));
Console.WriteLine(myExp.ValueOf("exp4"));
Console.WriteLine(myExp.ValueOf("exp5"));

结果

10
20
10 + 20 + 30
((10 + 20 + 30) / 10) - (10 + 20 + 30)
(10 + 20 + 30) / 10

【讨论】:

  • 在实际场景中,这些表达式将来自数据库,而不是这些硬编码的变量。第二个也是重要的事情是您正在更改表达式的顺序以使其变得容易。 Bur 的排序不能改变,并且在一个表达式中有 n 级求和表达式。之前的cmets我已经评论过了。
  • 好的,我明白了。我已根据您的 cmets 对我的回复进行了更改。
【解决方案2】:

字符串替换是不可避免的,但我们可以通过一个类来表示我们的表达式来减少循环和替换操作的数量。当我们将算术表达式包装在引用类型中时,我们可以添加特征并在以后使用它们:

public class ArithmeticExpression
{
    public string Value;
    public bool IsLiteral;
    public bool IsFinalized;

    public string ArithmeticSafeValue { get { return IsLiteral ? Value : "(" + Value + ")"; } }

    public ArithmeticExpression(string value)
    {
        Value = value;
        int dummy;
        IsFinalized = IsLiteral = int.TryParse(value, out dummy);
    }
}

下面是评估我们表达式最终版本的方法,将它们更改为文字:

private static void Resolve(Dictionary<string, ArithmeticExpression> expressions)
{
    foreach (string expressionKey in expressions.Keys.ToArray())
    {
        Resolve(expressionKey, expressions);
    }
}

private static void Resolve(string expressionKey, Dictionary<string, ArithmeticExpression> expressions)
{
    ArithmeticExpression expression = expressions[expressionKey];

    if (expression.IsFinalized) return;

    List<string> containedExpressions = expressions.Keys.Where(x => expression.Value.Contains(x)).ToList();
    if (containedExpressions.Count > 0)
    {
        containedExpressions.ForEach((x) => Resolve(x, expressions));
        containedExpressions.ForEach((x) => expression.Value = expression.Value.Replace(x, expressions[x].ArithmeticSafeValue));
        expression.IsFinalized = true;
    }
}

下面是如何使用它:

public static void Main()
{
    string exp1 = "10";
    string exp2 = "20";
    string exp3 = "exp1 + exp2 + 30";
    string exp4 = "exp5 - exp3";
    string exp5 = "exp3 / 10";

    Dictionary<string, ArithmeticExpression> expressions = new Dictionary<string, ArithmeticExpression>();
    expressions.Add("exp1", new ArithmeticExpression(exp1));
    expressions.Add("exp2", new ArithmeticExpression(exp2));
    expressions.Add("exp3", new ArithmeticExpression(exp3));
    expressions.Add("exp4", new ArithmeticExpression(exp4));
    expressions.Add("exp5", new ArithmeticExpression(exp5));

    Resolve(expressions);
    expressions.Keys.ToList().ForEach
    (
        (x) => Console.WriteLine("{0}: {1}", x, expressions[x].Value)
    );
}

希望这会有所帮助。

【讨论】:

  • 首先将表达式正确解析为某种抽象语法树然后用值渲染是非常好的。声明“替换是唯一的方法”使得这不是关于“解析”表达式的问题的答案。请注意,您可以编辑问题以匹配答案 - 因此这篇文章(和其他“答案”)将回答问题,并且更容易对重复提出异议并避免所有“最佳”争议。
  • 嗨@AlexeiLevenkov,我认为动词“解析”有点让我们成为语言障碍的受害者。包括我。因为在我看来,评估string exp4 = "exp5 - exp3"; 包括解析。那,“更换是不可避免的”是我的坏事。但实际上,我在副本中阅读了所有答案,但没有一个答案可以帮助这个(可能)非常年轻的人。它可以实现的是将问题更改为“好吧,我知道有这个和这个来解析和计算表达式,但是.. 我怎样才能在途中获取每个标记并将它们显示为单个算术公式?”。
  • 顺便说一句。也没有选民愿意改变这个问题的标题,这可能不是他们的责任。似乎在 Meta 上,我是按标题搜索的受害者(没有详细说明,也没有付出必要的努力),而“标题”又是这个问题被标记为重复的主要原因。不知道,真的没关系。
【解决方案3】:

嗯,不是最优雅的解决方案,但我认为它有效

            string exp1 = "10";
            string exp2 = "20";
            string exp3 = "exp1 + exp2 + 30";
            string exp4 = "exp5 - exp3";
            string exp5 = "exp3 / 10";         

            var dic = new Dictionary<String, String>();
            dic.Add("exp1", exp1);
            dic.Add("exp2", exp2);
            dic.Add("exp3", exp3);
            dic.Add("exp4", exp4);
            dic.Add("exp5", exp5);


            for (int i = 0; i < dic.Count; i++)          
            {
                var dicItem = dic.ElementAt(i);
                var splitted = dicItem.Value.Split(' ');
                var sb = new StringBuilder();
                foreach (var splittedItem in splitted)
                {                

                    if(dic.ContainsKey(splittedItem))
                    {
                        sb.Append(dic[splittedItem]);

                    }
                    else
                    {
                        sb.Append(splittedItem);
                    }
                    sb.Append(" ");

                }
                var parsed = sb.ToString();
                if (parsed.Contains("exp"))
                    i--; //go back and try to evaluate it again

                dic.Remove(dicItem.Key);
                dic.Add(dicItem.Key, sb.ToString());

            }

为了不使其更具可读性,您可以使用DynamicExpression

            var p = Expression.Parameter(typeof(String));
            foreach (var exp in dic.Values)
            {
                var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, exp);
                System.Diagnostics.Trace.WriteLine(e.Body);
            }

输出

10

20

((10 + 20) + 30)

((((((10 + 20) + (30 / 10)) - 10) + 20) + 30)

((10 + 20) + (30 / 10))

【讨论】:

  • 如果我在一个对象中有 200 多个表达式怎么办?它会是优化的解决方案吗?
  • 可能Stack 可以解决问题
【解决方案4】:

此解决方案仅替换变量:

var results = expressions.ToDictionary(x => x.Key, x => x.Value);

bool expanded;
do
{
    expanded = false;

    foreach (var key in expressions.Keys)
    {
        foreach (var kvpReplaceWith in expressions)
        {
            if (key == kvpReplaceWith.Key)
            {
                continue;
            }

            var lengthBefore = results[key].Length;
            results[key] = results[key].Replace(kvpReplaceWith.Key, "(" + kvpReplaceWith.Value + ")");
            var lengthAfter = results[key].Length;
            expanded = expanded || lengthBefore != lengthAfter;
        }

    }
}
while (expanded);

【讨论】:

  • 它会给我结果,但是这个解决方案的复杂性太高了。对于 5 个元素,它在第一个 foreach 循环中达到 15 次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-01
  • 2013-08-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-16
相关资源
最近更新 更多