Когда у меня есть некоторый алгоритм, который может изменять свое поведение в зависимости от дискретных элементов (таких как символы, действия и т.д.) вы можете увидеть если 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 };
}
}
Basi Как правило, мы говорим, что если токен закончил операцию, нам не нужен следующий токен, так как он будет свернут в текущее выражение: мы возвращаем 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;
}
}
Это делает для многословного, но красноречивого кода; личный вкус и профессиональная среда могут протестовать. Но я обнаружил, что он помогает выразить функциональные переходы между вашими государствами. Это также более легко проверить, поскольку каждое государство мало и не зависит от предыдущих состояний.
Я принял некоторые вольности, угадывая некоторые неясные моменты в вашем процессе (являются ли объединенные операторы недействительными?), И он не является полным (без скобок), но я думаю, что эта структура может помочь вам в виде потока токенов вы, кажется, имеете.
Почему именно вам нужны «действительные сигналы»? Для меня кажется, что в первый же проход можно было исключить пробелы; кроме того, двусмысленность '-', по-видимому, не может быть устранена, оба случая должны быть рассмотрены. – Codor
Будут ли элементы всегда иметь одну букву? Возможно, вы могли бы «поместить все значения в один список, удалить пробелы, вставить запятые между соседними буквами». Таким образом, '[" a "," + b "," c "]' становится '' a + bc "', который становится '" a + b, c "' – Kevin
@Codor ... thx .. возможно, я haven ' Это было очень ясно. Пространство эффективно представляет элемент массива, и мне нужно знать, какие элементы используются для доступа к другой информации об этом выражении. Так что просто конкатенация в строку, а затем синтаксический анализ и просмотр дерева выражений не дают мне необходимой информации. –