Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Piszemy trochę bardziej złożony kalkulator w C#.NET - #2

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:

 

windows programowanie

Komentarze

0 nowych
aeroflyluby   15 #1 20.02.2013 00:35

Za dużo umiesz :D

  #2 20.02.2013 14:38

Też tak chcę

kadet90   5 #3 20.02.2013 15:53

To w zasadzie bardzo proste, mogę napisać o podobnej bilbiotece do liczenia wyrażeń tylko że w php, oczywiście autorskiej, ale nie chce kraść autorowi tematu - pierwszy sobie go zaklepał :D

alucosoftware   7 #4 20.02.2013 15:58

Ładnie, zgrabnie, czysto i na temat.

W przypadku "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." - po prostu wykorzystaj inną nazwę zmiennej zamiast "i".

drobok   14 #5 20.02.2013 20:24

Jedno mi się tutaj nie podoba.
Mianowicie powinieneś zacząć od wprowadzenia danych. Ta twoja końcówka "Wyrażenie wygląda tak: {0}\nJego wartość to {1}\n" ma się nijak do tytułu. Piszesz kalkulator, tytuł to nie notacje postfixowe, czy coś w ten deseń.

pat.wasiewicz   5 #6 21.02.2013 07:44

Polecam przeczytać poprzedni mój wpis na blogu: dowiedziałbyś się, że pisanie kalkulatora jest rozłożone na 3 części, gdzie pierwsza to klasa Expression, druga to parsowanie wyrażeń postfiksowych, natomiast trzecia część to parsowanie wyrażeń infiksowych.