Blog (9)
Komentarze (73)
Recenzje (0)
@pat.wasiewiczPiszemy trochę bardziej złożony kalkulator w C#.NET - #2

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:

Wynik
Wynik
Wybrane dla Ciebie
Komentarze (6)