Last active
December 19, 2024 09:26
-
-
Save istupakov/c49ef290c3bc3dbe329bf68f67971470 to your computer and use it in GitHub Desktop.
C# realization of Shunting-yard algorithm
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Frozen; | |
using System.Text; | |
var text = Console.ReadLine()!; | |
using var reader = new StringReader(text); | |
var parser = new Parser(); | |
var tokens = parser.Tokenize(reader).ToList(); | |
Console.WriteLine(string.Join("\n", tokens)); | |
var rpn = parser.ShuntingYard(tokens); | |
Console.WriteLine(string.Join(" ", rpn.Select(t => t.Value))); | |
enum TokenType { Number, Variable, Function, Parenthesis, Operator, Comma, WhiteSpace }; | |
record Token(TokenType Type, string Value); | |
record Operator(string Name, int Precedence, bool RightAssociative = false); | |
class Parser | |
{ | |
private readonly IDictionary<string, Operator> _operatorsMap; | |
private static bool CompareOperators(Operator op1, Operator op2) | |
{ | |
return op1.RightAssociative ? op1.Precedence < op2.Precedence : op1.Precedence <= op2.Precedence; | |
} | |
private bool CompareOperators(string op1, string op2) => CompareOperators(_operatorsMap[op1], _operatorsMap[op2]); | |
private TokenType DetermineType(char ch) | |
{ | |
if (char.IsLetter(ch)) | |
return TokenType.Variable; | |
if (char.IsDigit(ch)) | |
return TokenType.Number; | |
if (char.IsWhiteSpace(ch)) | |
return TokenType.WhiteSpace; | |
if (ch == ',') | |
return TokenType.Comma; | |
if (ch == '(' || ch == ')') | |
return TokenType.Parenthesis; | |
if (_operatorsMap.ContainsKey(Convert.ToString(ch))) | |
return TokenType.Operator; | |
throw new Exception("Wrong character"); | |
} | |
public Parser() | |
{ | |
Operator[] operators = [ | |
new ("+", 1), | |
new ("-", 1), | |
new ("*", 2), | |
new ("/", 2), | |
new ("^", 3, true) | |
]; | |
_operatorsMap = operators.ToFrozenDictionary(x => x.Name); | |
} | |
public IEnumerable<Token> Tokenize(TextReader reader) | |
{ | |
var token = new StringBuilder(); | |
int curr; | |
while ((curr = reader.Read()) != -1) | |
{ | |
var ch = (char)curr; | |
var currType = DetermineType(ch); | |
if (currType == TokenType.WhiteSpace) | |
continue; | |
token.Append(ch); | |
var next = reader.Peek(); | |
var nextType = next != -1 ? DetermineType((char)next) : TokenType.WhiteSpace; | |
if (currType != nextType || currType == TokenType.Parenthesis) | |
{ | |
if (currType == TokenType.Variable && next == '(') | |
yield return new Token(TokenType.Function, token.ToString()); | |
else | |
yield return new Token(currType, token.ToString()); | |
token.Clear(); | |
} | |
} | |
} | |
public IEnumerable<Token> ShuntingYard(IEnumerable<Token> tokens) | |
{ | |
var stack = new Stack<Token>(); | |
foreach (var tok in tokens) | |
{ | |
switch (tok.Type) | |
{ | |
case TokenType.Number: | |
case TokenType.Variable: | |
yield return tok; | |
break; | |
case TokenType.Function: | |
stack.Push(tok); | |
break; | |
case TokenType.Comma: | |
while (stack.Peek().Value != "(") | |
yield return stack.Pop(); | |
break; | |
case TokenType.Operator: | |
while (stack.Count > 0 && stack.Peek().Type == TokenType.Operator && CompareOperators(tok.Value, stack.Peek().Value)) | |
yield return stack.Pop(); | |
stack.Push(tok); | |
break; | |
case TokenType.Parenthesis: | |
if (tok.Value == "(") | |
stack.Push(tok); | |
else | |
{ | |
while (stack.Peek().Value != "(") | |
yield return stack.Pop(); | |
stack.Pop(); | |
if (stack.Count > 0 && stack.Peek().Type == TokenType.Function) | |
yield return stack.Pop(); | |
} | |
break; | |
default: | |
throw new Exception("Wrong token"); | |
} | |
} | |
while (stack.Count > 0) | |
{ | |
var tok = stack.Pop(); | |
if (tok.Type == TokenType.Parenthesis) | |
throw new Exception("Mismatched parentheses"); | |
yield return tok; | |
} | |
} | |
} |
It was very useful for me thank you!
Thanks, this thread was useful for me. I appreciate that
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is also a bug in the tokenization, it doesn't parse (( correctly :)