Piszemy trochę bardziej złożony kalkulator w C#.NET - #2
19.02.2013 22:12
Zgodnie z zapowiedzią - kontynuuję dalej mój kalkulator.
Krótkie przypomnienie
W poprzednim poście (klik! ) stworzyłem prostą klasę Expression, która reprezentowała funkcję jednej zmiennej. Do tej pory, możliwe było tylko stworzenie wyrażenia za pomocą bezpośredniego zdefiniowania go w kodzie. Pora to zmienić
Odwrotna Notacja Polska
Do tej pory przyzwyczajeni byliśmy do takej postaci wyrażeń: argument1 operator argument2. Jest to tzw. notacja infiksowa (czyli operator jest w środku, pomiędzy argumentami). Natomiast wyrażenie w postaci ONP jest przedstawione następująco: argument1 argument2 operator. Stąd nazywa się ją notacją postfiksową. Jedną z zalet jest brak konieczności nawiasowania. Możemy jednoznacznie określić kolejność wykonywania operacji.
Więcej informacji jak i potrzebny algorytm znajdziemy tutaj. Nie będę zagłębiał się w szczegóły, przedstawię tylko jego implementację;
Założenia: otrzymamy ciąg przedstawiający wyrażenie zapisane w postaci ONP. Każdy argument i operator zostanie oddzielny dokładnie jednym białym znakiem.
Do naszej klasy Expression dodajmy następującą metodę:
public static Expression FromOnp(string input) { }
Pierwsze co przydało by się zrobić, to "pociąć" nasz łańcuch znakowy na poszczególne argumenty i operatory (tj. z wyrażenia "1 2 +" zrobić "1", "2", "+"). Wykorzystamy do tego celu wyrażenia regularne:
var arguments = Regex.Split(input, @"\s+");
Przyda się też stos, który będzie przechowywał nasze tymczasowe wyrażenia:
var stack = new Stack<Expression>();
Teraz przyszedł czas na główną pętlę:
Uwaga: w kodzie odwołanie do tablicy celowo będę rozpoczynał od nawiasu "wąsatego" bo blog traktuje mi w innym przypadku jako otwarcie znacznika.
I nie mam pojęcia jak temu zapobiec.
for (var i = 0; i < arguments.Length; i++) { var sym = arguments{i]; //jeżeli aktualny symbol jest operatorem if (Regex.IsMatch(sym, @"^[\+|\*|/]$")) { //weź dwa pierwsze elementy ze stosu var first = stack.Pop(); var second = stack.Pop(); switch (Convert.ToChar(sym)) //i wykonaj odpowiednie operacje dla tych dwóch wyrażeń { case '+': stack.Push(second.Add(first)); break; case '*': stack.Push(second.Multiply(first)); break; case '/': stack.Push(second.Divide(first)); break; } } //symbol nie jest operatorem else { if (Regex.IsMatch(sym, "^x$")) //na wejściu jest zmienna stack.Push(Variable.GetVariable); else //albo stała stack.Push(Constant.GetConstant(Convert.ToDouble(sym))); } }
Do sprawdzania zawrtości symbolu używałem oczywiście wyrażeń regularnych.
Teraz wystarczy nam zwrócić jedyny element ze stosu (dla poprawnie zapisanego wyrażenia w ONP powinien tam być dokładnie jeden element):
return stack.Peek();
Całość:
public static Expression FromOnp(string input) { var arguments = Regex.Split(input, @"\s+"); var stack = new Stack<Expression>(); for (var i = 0; i < arguments.Length; i++) { var sym = arguments{i]; //jeżeli aktualny symbol jest operatorem if (Regex.IsMatch(sym, @"^[\+|\*|/]$")) { //weź dwa pierwsze elementy ze stosu var first = stack.Pop(); var second = stack.Pop(); switch (Convert.ToChar(sym)) //i wykonaj odpowiednie operacje dla tych dwóch wyrażeń { case '+': stack.Push(second.Add(first)); break; case '*': stack.Push(second.Multiply(first)); break; case '/': stack.Push(second.Divide(first)); break; } } //symbol nie jest operatorem else { if (Regex.IsMatch(sym, "^x$")) //na wejściu jest zmienna stack.Push(Variable.GetVariable); else //albo stała stack.Push(Constant.GetConstant(Convert.ToDouble(sym))); } } return stack.Peek(); }
Przykład
Teraz do naszego pliku Program.cs możemy dopisać jakąś prostą konwersję:
Expression expr4 = Expression.FromOnp("12 2 3 4 * 10 x / + * +"); Console.Write("Wyrażenie wygląda tak: {0}\nJego wartość to {1}\n" + "Pochodna to {2}\nCałka od 1 do 2 to {3}\n\n", expr4, expr4.Calculate(4), expr4.Derivative(), expr4.Integral(1, 2));
A naszym oczom powinien ujrzeć się piękny widok: