非结构化字符串串联的策略

本文关键字:策略 结构化 字符串 | 更新日期: 2023-09-27 18:19:31

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

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

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

但这一切都感觉有点混乱。例如

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

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

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

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

Thx提前

S

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

非结构化字符串串联的策略

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

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;
    }

当我有一些算法可以根据离散元素(如令牌、操作等)改变其行为时,你可以看到State设计模式是否很匹配。

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

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;
    }
}

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

我在流程中的一些不清楚的地方进行了一些随意的猜测(串联运算符无效吗?),而且它并不完整(没有括号),但我认为这种结构可以帮助您获得您似乎拥有的那种令牌流。

如果删除所有空白,则所有有效字符串都是奇数。接下来,您需要检查是否所有奇数位置都是leter(a、b等),偶数位置都是有效字符(+、-、等)。