非结构化字符串串联的策略
本文关键字:策略 结构化 字符串 | 更新日期: 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等),偶数位置都是有效字符(+、-、等)。