非结构化字符串连接策略

时间:2014-08-27 11:51:37

标签: c# algorithm

我有非结构化字符串的数组(不同长度),我希望将其连接成字符串表达式以便解析。一些例子可能是

a  +b              => a+b
a+  b              => a+b
a  +b   c  +d      => a+b, c+d
a+  b   c+  d      => a+b, c+d
a+  b  +c   d      => a+b+c, d
a  +b  +c   d      => a+b+c, d
a+  b+  c  +d      => a+b+c+d
a  +b  +c  +d      => a+b+c+d
a  +b+  c+  d      => a+b+c+d
a  +b   c   d      => a+b, c, d

注意:a,b,c和d用于简洁。它们实际上可以是任何长度的字符串。此外,它们中也可能有任意数量......不仅仅是4。

请注意,该元素可以具有前导或尾随运算符,这些运算符将确定它是否应该连接到数组中的前一个或后一个项,以确定它是独立的还是下一个表达式的一部分。 (一元运算符也存在固有的模糊性,并决定是否

a -b => a-b or a, -b

我确实有一个语法(反语),我目前用它来确定构建的表达式是否格式正确。所以我连接每个元素,一次一个,解析连接的结果,看它是否格式正确。如果格式良好,我仍然需要继续使用元素,以防下一个元素具有前导运算符。一旦我从解析器得到2个无效结果(或者数组没有更多元素),我得出结论:表达式(最后2个连接)是有效的,存储它然后重新开始。 (我需要这样做,因为我需要知道有效的表达式是数组中特定元素的串联,因为它们映射回具有其他信息的对象。)

但这一切都让人觉得有点笨拙。例如,在

的情况下
a +b +c +d 

a          => valid
a +b       => valid
a +b +c    => valid
a +b +c +d => valid

我会得到4个有效的"信号"但对于底层表达式,只有最后一个是"真正的"有效"信号"

我想知道是否还有其他更优雅的策略来确定我是否应该连接。也许我没有完全使用解析器,或者有一些我不熟悉的模式匹配策略?

那我该如何处理这个问题?

提前谢谢

取值

PS我正在使用C#,但我不一定认为这种情况与此相关。

3 个答案:

答案 0 :(得分:2)

这应该有效,注意这段代码如何处理一元运算符

static List<string> GetExpressions(string[] stringArray)
    {
        const string operators = "+-*/=";
        const string unaryOps = "+-";
        var q = new Queue<string>(stringArray.Length*2);

        foreach (string s in stringArray)
        {
            var work = s;
            if (operators.Contains(work[0]))
            {
                q.Enqueue(work[0].ToString());
                work = work.Substring(1);
            }
            if (operators.Contains(work[work.Length-1]))
            {
                q.Enqueue(work.Substring(0, work.Length - 1));
                q.Enqueue(work[work.Length - 1].ToString());
                continue;
            }
            q.Enqueue(work);
        }

        var res = new List<string>();
        var tmpString = new StringBuilder();
        var lastState = "Op";

        while (q.Count > 0)
        {
            var currElem = q.Dequeue();
            var currState = "St";
            if (unaryOps.Contains(currElem))
                currState = "Un";
            else if (operators.Contains(currElem))
                currState = "Op";

            switch (lastState + currState)
            {
                case "OpUn":
                case "OpSt":
                case "UnUn": // only with + & - unary ops: refinement necessary
                case "UnSt":
                case "StUn": // only with + & - unary ops: refinement necessary
                case "StOp":
                    tmpString.Append(currElem);
                    break;
                case "StSt":
                    res.Add(tmpString.ToString());
                    tmpString.Length=0;
                    tmpString.Append(currElem);
                    break;
                case "OpOp":
                case "UnOp":
                    throw new Exception();
            }
            lastState = currState;
        }

        res.Add(tmpString.ToString());

        return res;
    }

答案 1 :(得分:1)

似乎如果删除所有空格,则所有有效字符串都是奇数。接下来你需要检查所有奇数位置是否是leters(a,b等),偶数位置是有效的chars(+, - ,, etc)。

答案 2 :(得分:1)

当我有一些算法可以根据离散元素(例如标记,动作等)改变其行为时,你可以看到State design pattern是否是一个很好的匹配。

与其他方法相比,该模式有点冗长,但如果需要,可以很容易地扩展。我们从一个抽象的状态类开始:它的目标是让一个新的令牌发挥作用时让你从一个状态转换到另一个状态:

public abstract class State
{
    public static string[] operators = new string[] { "+", "-", "*", "/" };
    public List<string> Expressions { get; set; }
    public List<string> Tokens { get; set; }
    public abstract State Process(string token);
}

我们可以拥有的每个国家都将来自这个国家;我们可以尝试预先建模:我们基本上可以描述两种情况

  • 要么我们不关心下一个标记,因为前一个标记以操作符结束(或者我们正在开始我们的第一个表达式)
  • 或者如果我们想要继续表达式
  • ,我们希望下一个令牌以操作符开头

让我们创建第一个州:

public class WaitingForAnyTokenState : State
{
    public override State Process(string token)
    {
        return PushTokenToTokenList(token);
    }

    protected State PushTokenToTokenList(string token)
    {
        Tokens.Add(token);
        if (operators.Any(op => token.EndsWith(op)))
        {
            return new WaitingForAnyTokenState() { Expressions = Expressions, Tokens = Tokens };
        }
        return new WaitingForOperationState() { Expressions = Expressions, Tokens = Tokens };
    }
}

基本上,我们说如果令牌以操作结束,我们就不会关心下一个令牌,因为它会被折叠到当前表达式中:我们返回WaitingForAnyTokenState

相反,如果我们不以某个操作结束,当前表达式会发生什么取决于下一个标记。如果它以操作开始,则表达式继续。如果没有,则当前表达式结束,我们开始一个新表达式。

public class WaitingForOperationState : State
{
    public override State Process(string token)
    {
        CloseCurrentExpression(token);
        return PushTokenToTokenList(token); // let's imagine the same method as above is accessible here
    }

    private void CloseCurrentExpression(string token)
    {
        if (!operators.Any(op => token.StartsWith(op)))
        {
            CombineTokensIntoExpression();
            Tokens = new List<string>();
        }
    }
}

有趣的是,下一个案件仍然以与第一个州相同的方式决定。唯一改变的是在必要时关闭当前表达式。

以下是您可以使用的架构的示例代码:

private static void Main(string[] args)
{
    var ttea = new TokenToExpressionAggregator();
    foreach (var l in new string[] { "a+", "+1", "+c-", "d", "e", "+d", "z+", "a+" }) {
        ttea.Add(l);
    }
    ttea.EndAggregation();
    foreach (var expression in ttea.CurrentState.Expressions) {
        Console.WriteLine(expression);
    }
}

public class TokenToExpressionAggregator
{
    public State CurrentState { get; set; }
    public TokenToExpressionAggregator()
    {
        CurrentState = new InitialState();
    }
    public void Add(string token)
    {
        CurrentState = CurrentState.Process(token);
    }
    public void EndAggregation()
    {
        CurrentState = new FinalState(CurrentState);
    }
}

public abstract class State
{
    public static string[] operators = new string[] { "+", "-", "*", "/" };
    public List<string> Expressions { get; set; }
    public List<string> Tokens { get; set; }
    public abstract State Process(string token);

    protected State PushTokenToTokenList(string token)
    {
        Tokens.Add(token);
        if (operators.Any(op => token.EndsWith(op)))
        {
            return new WaitingForAnyTokenState() { Expressions = Expressions, Tokens = Tokens };
        }
        return new WaitingForOperationState() { Expressions = Expressions, Tokens = Tokens };
    }

    protected void CombineTokensIntoExpression()
    {
        Expressions.Add(string.Join(" ", Tokens.ToArray()));
    }
}

public class InitialState : WaitingForAnyTokenState
{
    public InitialState()
    {
        Expressions = new List<string>();
        Tokens = new List<string>();
    }
}

public class WaitingForAnyTokenState : State
{
    public override State Process(string token)
    {
        return PushTokenToTokenList(token);
    }
}

public class WaitingForOperationState : State
{
    public override State Process(string token)
    {
        CloseCurrentExpression(token);
        return PushTokenToTokenList(token);
    }

    private void CloseCurrentExpression(string token)
    {
        if (!operators.Any(op => token.StartsWith(op)))
        {
            CombineTokensIntoExpression();
            Tokens = new List<string>();
        }
    }
}

public class FinalState : State
{
    public FinalState(State state)
    {
        Expressions = state.Expressions;
        Tokens = state.Tokens;
        CombineTokensIntoExpression();
        Tokens = null;
    }

    public override State Process(string token)
    {
        return this;
    }
}

它提供了冗长但雄辩的代码;个人品味和专业环境可能会抗议。但我发现它有助于表达你的状态之间的功能转换。它也更容易测试,因为每个州都很小,并且不依赖于以前的状态。

我已经采取了一些自由来猜测你的过程中的一些不明确的点(连接的运算符是无效的吗?)并且它不完整(没有括号),但我认为这种结构可以帮助你获得那种令牌你似乎有流。