Autor Beitrag
EdiZ84
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Do 07.05.09 16:33 
hallo liebes Forum. Ich hatte vor einiger zeit angesprochen das ich ein Parser erstellen wollte allerdings noch überhaupt keine Kenntnisse in C# habe.

Nun habe ich mit dem Buch (Richtig einsteigen: Programmieren lernen mit C# 2005) schon angefangen und merke erste Fortschritte. Allerdings möchte ich wissen ob ich überhaupt das richtige für den Parser lerne. Ich bearbeite gerade das Kapitel "Zeichenfolgen bearbeiten".

-Es ist richtig die eingegebenen Funktion (2+5)*3 erstmal mit split zu trennen wie z.B.

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
//Zeichenfolgen zerlegen (Konsolenanwendung)

string eingabe3 = Console.ReadLine();
string[] aufgabe;


char[] trennzeichen = { ' ','+','-','(',')','/',':','*'}; //Trennzeichen für die Zerlegung
aufgabe = eingabe3.Split(trennzeichen, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(eingabe3);

for (int i = 0; i < aufgabe.Length; i++)
Console.WriteLine("{0}. Teilsting: -{1}-", i + 1, aufgabe[i]);
Console.WriteLine("");


char[] myCharArr = eingabe3.ToCharArray();
for (int i = 0; i <= myCharArr.GetUpperBound(0); i++)
Console.WriteLine(myCharArr[i]);
Console.Read();



-Die Trennzeichen sind bei mir die Operatoren wie benutze ich diese jetzt wenn ich die Funktion ausrechnen will????
-Wie mache ich das mit der Punkt vor Strich-Rechnung ?????
-Gibt es ein Buch was speziell alles von Anfang(für Anfänger) bis zum Ende alles erklärt?????

Ich weis es wird in vielen Foren darüber diskutiert, aber da ich noch null Ahnung habe, verstehe ich den Code nicht.

Könnten ihr mir wenigstens sagen ob ich die Aufgabe richtig anpacke, oder bin ich ganz falsch???
Habt ihr irgendetwas über die parser Theorie????

Vielen Dank im Voraus.

Mfg
Edgar

Moderiert von user profile iconChristian S.: Delphi-Tags hinzugefügt
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Do 07.05.09 19:13 
Hey,

ich würde erstmal Formeln induktiv definieren:

1. x ist eine Formel (x ist eine Zahl)
2. Sind x und y Formeln, so ist (x op y) auch eine Formel, mit op € {+, -, *, /, ^}

Würde dann also prinzipiell so laufen:
Du gibst ein: ((2 + 5)*3)
Dein Programm sieht: Die Formel fängt mit einem "(" an, also kommen 2 Formeln, und ein Operand in der Mitte.
Also liest es x ein:
x fängt wieder mit "(" an, ist also wieder eine zusammengesetzte Formel.
Also wird erst x ausgelesen (2), dann der Operand (+), dann y(5), was du zu 7 auswerten kannst.

Dann gehst du zurück zum Auswerten der ersten Formel, das x hast du jetzt ja als 7 berechnet, als nächstes kommt der Operand, "*", und dann y, was einfach die 3 ist.
Dies wertest du aus zu 7*3 = 21, fertig :)

Etwas komplizierter wird es, wenn du
- 1 + 2 + 3 statt ((1 + 2) + 3)
- 1 * 2 + 3 statt ((1 * 2) + 3) eingeben willst, dann muss dein Programm halt auch wissen, in welcher Reihenfolge die Operanden auszuwerten sind, und dass eine Formel auch aus mehr als 2 Operanden bestehen kann.
Einfach ist dies z.B. in der polnischen Notation.

Ich hoffe, ich konnte dir einen kleinen Einblick geben ;)
Jakob_Ullmann
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1747
Erhaltene Danke: 15

Win 7, *Ubuntu GNU/Linux*
*Anjuta* (C, C++, Python), Geany (Vala), Lazarus (Pascal), Eclipse (Java)
BeitragVerfasst: Do 07.05.09 19:15 
also mein Ansatz (der auch klappt) ist, das Teil rekursiv auseinanderzunehmen. Also bei 2*3+5 wird zuerst nach + gesucht, der Teil davor wird ausgewertet und der danach ebenso, die Rückgabewerte werden addiert. Bei 2*3 ist weder + noch - vorhanden, deshalb wird nach * gesucht und der Teil links davon (2) und der rechts (3) werden ausgewertet und die Ergebnisse multipliziert. Und so verzweigt sich das immer mehr, bis man einen String nicht weiter zerlegen kann, weil er eine bloße Zahl ist.

Klammern sind etwas anspruchsvoller, gehen aber im Prinzip genauso.
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Do 07.05.09 19:23 
Was macht dein Parser bei nicht assoziativen Operationen?

edit: Erst denken, dann schreiben :)
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Do 07.05.09 20:42 
Hi Leute, war kurz arbeiten.

Erstmal danke für die schnelle Hilfe. Aber genau bei diesen Problem komme ich ja nicht weiter. Wie teile ich den String in die Einzel-String richtig und wie benutzte ich dann die Operatoren?????

Mein Anfang:

und zwar, lese ich erstmal irgendeine Gleichung mit Console.Readline ein. z.B. (5+5)*2. Dann teile ich die Aufgabe in die Einzel-Stings (Die Trennzeichen sind die Operatoren (siehe Code)(Ist das Richtig????). Die einezelnen Teile werden in ein Array gespeichert, auf die ich auch zugreifen kann. Aber ich kann nicht auf die Operatoren zugreifen. Wie mache ich das????? wie kann ich auf die einzelnen Operatoren zugreifen. Ihr könnt ja mein Code ausprobieren und mir dann was dazu schreiben. (Dann sieht ihr auch das ich noch nicht so weit bin).


Wäre euch echt sehr verbindlich wenn ihr mir einen Beispiel-Code in C# schicken könntet. Nur einen Code der nachvollziehbar ist, wo eine Gleichung in die Einzelteile zerlegt wird und wo dann die Operatoren für die Rechnnung benutzt werden.

Mein Ziel ist es die Sachen zu verstehen, und nicht mit fertigen Codes zu arbeiten die andere erstellt haben.
Brauche ein Codebeispiel zu lernen.

Das mit dem Buch(Ich meine ein spezielles C# Buch für Mathematische Gleichungen.)

Danke im Voraus.

mfg Edgar

Ciao
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Do 07.05.09 22:47 
Bin gerade dabei wieder eine VM mit VS aufzusetzen, wenn das nicht zu lange dauert mach ich schnell noch was fertig...

edit: Ich bezweifle, dass es ein Buch über Gleichungen in C# gibt... Meiner Meinung nach sind das Verständnis für die Modelierung solcher Probleme und die Kenntnis einer speziellen Sprache zwei Paar Schuhe...
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Do 07.05.09 22:56 
Hi Thorsten, ich danke dir auf jeden Fall für deine Mühe.
Bitte um Verständnis, hab vorher nur mit vba in excel was gemacht und das erst seit ein Jahr, hab echt nur einwenig Kenntnisse. Beschäftige mich auch erst seit einen Monat mit c#.

Brauche den Parser für meine Ausbildung. Wenn ich das schaffe bekomme ich diese. Daher will ich das auch (zum Teil) selbst erledigen.

Nochmal besten Dank.
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Fr 08.05.09 01:03 
Hey,
nachdem die Installation Stunden (blödes Mergen...)gedauert hat hier eine schnell zusammengeklatschte Version.
Btw.: Irgendwie konnte ich nicht mit Pointern arbeiten, wisst ihr wo man das in VS2008 umstellen kann?

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
class Program
    {
         
        int result = 0;
        int nextIndex = 0;
        void check(char[] letters)
        {
            if (nextIndex > letters.Length)
            {
                throw new Exception("Error parsing formula");
            }
        }
        Boolean number(char c)
        {
            return c >= '0' && c <= '9';
        }
        int read(char[] letters)
        {
            char c = letters[nextIndex];
            // is c the beginning of a number?
            if (number(c))
            {
                return readNumber(letters);
            }
            // so the function's reading op1 ° op2
            if (letters[nextIndex] != '(')
            {
                Console.WriteLine("error parsing formula...");
                return 0;
            }
            check(letters);
            // current letter is '(', calculate following one's...
            nextIndex++;
            // read op 1
            int op1 = read(letters);
            // read °, go one letter ahead
            char op = letters[nextIndex++];
            // read op2
            int op2 = read(letters);
            // step over following ')'
            nextIndex++;
            // calculate result:
            switch (op)
            {
                case '+':
                    return op1 + op2;
                case '-':
                    return op1 - op2;
                case '*':
                    return op1 * op2;
                case '/':
                    return op1 / op2;
                default:
                    return 0;
            }
        }
        int readNumber(char[] letters)
        {
            int result = 0;
            // letters[nextIndex] is a number, otherwise this function wouldn't be called:
            do
            {
                result *= 10;
                result += letters[nextIndex] - '0';
                nextIndex++;
            } while (nextIndex < letters.Length && number(letters[nextIndex]));
            return result;
        }
        static void Main(string[] args)
        {
            Console.WriteLine("Formel eingeben...");
            char[] letters = Console.ReadLine().ToCharArray();
            Program p = new Program();
            int result = p.read(letters);
            Console.Write(result);
            Console.ReadLine();
        }
    }
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19346
Erhaltene Danke: 1754

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Fr 08.05.09 01:17 
Um Pointer zu benutzen musst du den entsprechenden Codeblock oder die Methode als unsafe ausweisen, also public unsafe DoSomething() z.B., aber besser ist es einfach gar keine zu verwenden in C#. ;-)
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Fr 08.05.09 11:17 
user profile iconEdiZ84 hat folgendes geschrieben Zum zitierten Posting springen:
Das mit dem Buch(Ich meine ein spezielles C# Buch für Mathematische Gleichungen.)
Terme, nicht Gleichungen. Ein ganzes Anfängerbuch dazu wirst du wie schon gesagt nicht finden, aber hier findest du einige Links zu Artikeln über das Thema.



user profile iconThorsten83 hat folgendes geschrieben Zum zitierten Posting springen:
[...]hier eine schnell zusammengeklatschte Version.
Da gibt es aber noch einiges zu tun - bei "1+1" fängts schon an :zwinker: .

_________________
>λ=
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Fr 08.05.09 13:45 
Nach obiger Definition ist 1+1 auch keine gültige Formel :D
Jakob_Ullmann
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 1747
Erhaltene Danke: 15

Win 7, *Ubuntu GNU/Linux*
*Anjuta* (C, C++, Python), Geany (Vala), Lazarus (Pascal), Eclipse (Java)
BeitragVerfasst: Fr 08.05.09 19:03 
user profile iconThorsten83 hat folgendes geschrieben Zum zitierten Posting springen:
Was macht dein Parser bei nicht assoziativen Operationen?


Wie meinst du das?
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Mo 11.05.09 13:09 
Hi Leute erstmal besten dank, vorallen dingen dir Thorsten für den Code. Werde den aufjeden Fall studieren. Aber ich habe mal was komplett selber versucht, nach der Theorie von Jakob_Ullmann und weil so ein ähnliches Thema in meinen Buch behandelt wird.

Mein Ergebnis: (Funktioniert immoment nur wenn man die Gleichung "Zahl+Zahl*Zahl" eingibt.)
Wenn man dies eingibt zerteilt er alles komplett und findet auch die Operatoren.

Code:

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] aufgabe;//Speichert die Teile der Aufgabe
            char[] trennzeichen = { ' ', '+', '-', '(', ')', '/', ':', '*' };  //Trennzeichen für die Zerlegung 
            
            string localpart, domainpart, localpart1, domainpart1 = "";
            string ergebnis = "";

            int zwischenergebnis = 0;
            int pos, pos1 = 0;
            int operand1, operand2, operand3, operand4 = 0;

            Console.WriteLine("Geben Sie eine Gleichung ein.");
            string eingabe = Console.ReadLine();
            aufgabe = eingabe.Split(trennzeichen, StringSplitOptions.RemoveEmptyEntries);

            pos = eingabe.IndexOf("+");
            localpart = eingabe.Substring(0, pos);
            domainpart = eingabe.Substring(pos + 1);

            pos1 = domainpart.IndexOf("*");
            localpart1 = domainpart.Substring(0, pos1);
            domainpart1 = domainpart.Substring(pos1 + 1);

            operand1 = int.Parse(aufgabe[0]);
            char operant1 = eingabe[pos];
            string aktion1 = operant1.ToString(); 
            operand2 = int.Parse(aufgabe[1]);

            char operant2 = domainpart[pos1];
            string aktion2 = operant2.ToString();
            operand3 = int.Parse(aufgabe[2]);


            switch (aktion1)
            {
                case "+":
                    zwischenergebnis = operand1 + operand2;
                    break;
                case "-":
                    zwischenergebnis = operand1 + operand2;
                    break;
                case "*":
                    zwischenergebnis = operand1 + operand2;
                    break;
                case ":":
                case "/":
                    zwischenergebnis = operand1 + operand2;
                    break;
                default:
                    ergebnis = "Fehler: ungültiger Operator!";
                    break;
            }

            switch (aktion2)
            {
                case "+":
                    ergebnis = "Ergebnis: " + (zwischenergebnis + operand3);
                    break;
                case "-":
                    ergebnis = "Ergebnis: " + (zwischenergebnis - operand3);
                    break;
                case "*":
                    ergebnis = "Ergebnis: " + (zwischenergebnis * operand3);
                    break;
                case ":":
                case "/":
                    ergebnis = "Ergebnis: " + (zwischenergebnis / operand3);
                    break;
                default:
                    ergebnis = "Fehler: ungültiger Operator!";
                    break;
            }

            Console.WriteLine("1.Zerteilung");
            Console.WriteLine(localpart);
            Console.WriteLine(domainpart);
            Console.WriteLine(aktion1);
            Console.WriteLine(zwischenergebnis);

            Console.WriteLine("2.Zerteilung");
            Console.WriteLine(localpart1);
            Console.WriteLine(domainpart1);
            Console.WriteLine(aktion2);
            Console.WriteLine(ergebnis);

            Console.Read();
        }
    }
}


Wenn ihr den Code mal ausprobiert zerteilt er alles in Einzelstrings, rechnet es auch(Punkt vor Strich-Rechnung wird noch nicht beachtet), und gibt die Ergebnisse aus.

Meine Fragen, dazu sind folgende:
Der erste Operator wird mit variable.indexof("+") gesucht. Geht das nicht anders???? z.B. so variable.indexof("operatorX")???

Das ist auch immoment das Problem, wenn ich beispielweise "zahl1-zahl2*33" eingebe gibt es eine Ausnahme.

Hab schon versucht es mit ner if else anweisung zu regeln, aber klappt nicht. z.B.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
if (eingabe.indexof("+") != 1) //Rückgabewert bei nicht finden der Zeichenfolge = 1
    //Anweisung

else if (eingabe.indexof("-") != 1) 
    //Anweisung



Leider klappt das nicht, weis nicht warum, ihr aber bestimmt.

Wie könnte ich das den anders gestalten??????

Falls ich mich nicht verständlich ausgedrückt habe, einmal kurz melden.

Ok, danke euch , ciao

Moderiert von user profile iconKha: C#-Tags hinzugefügt
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Mo 11.05.09 14:57 
user profile iconJakob_Ullmann hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconThorsten83 hat folgendes geschrieben Zum zitierten Posting springen:
Was macht dein Parser bei nicht assoziativen Operationen?

Wie meinst du das?

Naja, die Frage ist halt, wie z.B. 3 - 2 - 1 ausgewertet wird, da ja (3 - 2) - 1 != 3 - (2 - 1)
Die Frage stellt sich ohne Klammerung in jeder Notation.

@Edi:
Magst du deinen Code als Code formatieren?
Das funktioniert so jetzt aber nur mit genau dem einen Term, oder?

Was du natürlich machen kannst ist folgendes:
ausblenden Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
char[] operators = {'^', '*', '/', '+', '-');
for(int i = 0 ; i<operators.length ; i++)
{
   int index ;
   while((index = term.indexOf(operators[i]) )> 0)
   {
      // do something
   }
}

Wie man dann weitermacht ist halt Geschmackssache, ich hab mal was mit einer textuellen Ersetzung probiert, klappte ganz gut, eignet sich aber so, wie ich's jetzt habe, nur für infix-Operatoren... (Also z.B. sin(3) würde damit nicht funktionieren)
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Mo 11.05.09 17:38 
Hi Thorsten , danke dir nochmal, hab dein Code ausprobiert, und versuche den jetzt erstmal in meinen zu integrieren.

Zitat von Thorsten:
Magst du deinen Code als Code formatieren?

Was meinst du damit, ob es hier im Form als Code angezeigt werden soll?????

Zitat von Thorsten:
Das funktioniert so jetzt aber nur mit genau dem einen Term, oder?

Ja die Aufgabe funktioniert momentan nur mit einem Therm.
Muss ja noch einbisschen daran arbeiten.

Versuch das erstmal. Danke dir nochmal. Finde ich echt korrekt von dir. Du hast auf jeden Fall ein gut bei mir.

Ok,ciao
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Mo 11.05.09 17:56 
Kein Ding, dafür ist das Forum ja da ;)

Möchtest du meinen aktuellen Source haben? Der läuft halt ungefähr vollständig auf char-Arrays, und nimmt da Ersetzungen vor...
Er kann bis jetzt: ^, *, /, +, - (:)), und sowohl mit als auch ohne Klammern eine korrekte Auswertung vornehmen (Klammern vor ^ vor Punktrechnung vor Strichrechnung, Klammern sind nur noch für die Prioritäten notwendig, 1 + 2 + 3 würde er also auch verstehen...)
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Di 12.05.09 14:06 
Hi Thorsten :)

also dazu sage ich nie nein. Klar, wenn du in mir schicken könntest. Falls du das auf meine E-Mail machen möchtest, kannst du das auf die Adresse E.Zweer@gmx.de schicke.

Danke dir für das Angebot.

Ciao
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Di 12.05.09 19:30 
user profile iconThorsten83 hat folgendes geschrieben Zum zitierten Posting springen:
Möchtest du meinen aktuellen Source haben? Der läuft halt ungefähr vollständig auf char-Arrays, und nimmt da Ersetzungen vor...
Wäre schön, wenn du zumindest die finale Version dann auch fürs restliche Forum zugänglich machen würdest :) .
Und ich weiß jetzt nicht, welchen Algorithmus du genau verwendest, aber ich würde doch einen theoretisch fundierten empfehlen. Natürlich ist String-Manipulation am Anfang eingängiger als irgendwelche komischen Grammatiken, aber das Ergebnis lässt imo an Eleganz und auch Klarheit nichts zu wünschen übrig. Oder hat hier irgendjemanden etwas gegen meinen kleinen Recursive Descent Parser ;) ?
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
    static void Main(string[] args)
    {
      Console.WriteLine(Parse("1+2*(5*4+2)*3"));
    }

    // "Einlesen" (consume) == Einen Anfang (substring) des String verarbeiten und den Rest über ref zurückgeben

    // Liest das nächste (vorgegebene) Zeichen des Strings ein
    static void Read(ref string s, char c)
    {
      if (s[0] != c)
        throw new Exception();
      s = s.Substring(1);
    }

    // end of file
    static bool Eof(string s)
    {
      return s == "";
    }

    // false, wenn Read(ref s, c) fehlschlagen würde
    static bool TryRead(ref string s, char c)
    {
      bool success = !Eof(s) && s[0] == c;
      if (success)
        s = s.Substring(1);
      
      return success;
    }

    // Liest eine Dezimalzahl ein
    // "123abc" -> 123, "abc"
    static int Number(ref string s)
    {
      int num = int.Parse(new string(Enumerable.TakeWhile(s, Char.IsDigit).ToArray()));
      s = new string(Enumerable.SkipWhile(s, Char.IsDigit).ToArray());
      return num;

      // oder auch

      Match match = Regex.Match(s, @"^(\d+)(.*$)");
      s = match.Groups[2].Value;
      return int.Parse(match.Groups[1].Value);
    }

    // Liest "Number" oder "(Expression)" ein
    // "3*5" -> 3, "*5"
    // "(1+2)*3" -> 3, "*3"
    static int Factor(ref string s)
    {
      if (TryRead(ref s, '(')) {
        int num = Expression(ref s);
        Read(ref s, ')');
        return num;
      }
      else
        return Number(ref s);
    }

    // Liest mit "*" und "/" verkettete Factors ein
    static int Term(ref string s)
    {
      int num = Factor(ref s);
      while (true)
        if (TryRead(ref s, '*'))
          num = num * Factor(ref s);
        else if (TryRead(ref s, '/'))
          num = num / Factor(ref s);
        else
          return num;
    }

    // Liest mit "+" und "-" verkettete Terms ein
    static int Expression(ref string s)
    {
      int num = Term(ref s);
      while (true)
        if (TryRead(ref s, '+'))
          num = num + Term(ref s);
        else if (TryRead(ref s, '-'))
          num = num - Term(ref s);
        else
          return num;
    }

    static int Parse(string expression)
    {
      int result = Expression(ref expression);
      if (!Eof(expression)) throw new ArgumentException("expression");

      return result;
    }


Natürlich nicht ganz vollständig, aber durch die klare Struktur ist das Erweitern kein großes Problem.

_________________
>λ=
Thorsten83
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 191
Erhaltene Danke: 1



BeitragVerfasst: Di 12.05.09 22:59 
user profile iconKha hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconThorsten83 hat folgendes geschrieben Zum zitierten Posting springen:
Möchtest du meinen aktuellen Source haben? Der läuft halt ungefähr vollständig auf char-Arrays, und nimmt da Ersetzungen vor...
Wäre schön, wenn du zumindest die finale Version dann auch fürs restliche Forum zugänglich machen würdest :) .
Und ich weiß jetzt nicht, welchen Algorithmus du genau verwendest, aber ich würde doch einen theoretisch fundierten empfehlen. Natürlich ist String-Manipulation am Anfang eingängiger als irgendwelche komischen Grammatiken, aber das Ergebnis lässt imo an Eleganz und auch Klarheit nichts zu wünschen übrig.

Ich kann ihn auch direkt hier posten, aber bitte nicht hauen ;)
Die Idee ist folgende:
Das Programm liest einen Term ein, z.B. "3 + (5*4 + 1)^2"
Im ersten Schritt wird ein ')' gesucht, dann das davor liegende '('.
Der dazwischen liegende Term wird ausgewertet, und das Ergebnis in den Ursprungsterm eingesetzt, heraus kommt also
3 + 21^2
Es wird nochmal ein ')' gesucht, aber keines gefunden, also wird der Endterm ausgewertet.
Wir haben diese Art der textuellen Ersetzung im ersten Semester mal als applikative Auswertung behandelt, angeblich sollen manche Scheme-Interpreter so arbeiten, allerdings dann in polnischer Notation (womit sich das Problem der Prioritäten der Operatoren halt erledigt...)
Allerdings hab ich halt auf die schnelle ein paar Funktionen zusammengeklatscht, die z.B. den "Startindex" einer Zahl in einem char-Array zurückgeben, hätte man evtl. auch schöner machen können.

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Media;
namespace Parser
{
     class Program
    {
        char[]      operators = { '^', '*', '/', '+', '-' };
        private bool check(char[] chars)
        {
            int         tmp = 0;
            int         i;
            for (i = 0; i < chars.Length; i++)
            {
                if (chars[i] == '(')
                {
                    tmp++;
                }
                else if (chars[i] == ')')
                {
                    tmp--;
                }
                if (tmp < 0)
                {
                    return false;
                }
            }
            return tmp == 0;
        }
        private int parse(String str)
        {
            int             tmp = 0;
            int             start, end;
            char[]          chars;
            // remove all blancs:
            str.Replace(" ", "");
            // check if this can be a valid term:
            // for each opening bracket there has to be a closing one:
            chars = str.ToCharArray();
            if (!check(chars))
            {
                Console.WriteLine("Formula isn't valid...");
                return 0;
            }
            // look for terms in brackets, evaluate them from inside to outside:
            while((end = getFirst(chars, ')')) > 0)
            {
                start   = getClosest(chars, end, '(');
                tmp     = calc(chars, start + 1, end - 1);
                chars   = replace(chars, start, end, tmp.ToString().ToCharArray());
            }
            return calc(chars, 0, chars.Length-1);
        }
        private int calc(char[] chars, int start, int end)
        {
            // get a copy of the relevant area:
            char[]      copy = new char[end - start + 1]; 
            int         i;
            int         index = 0;
            int         pos;
            bool        replaced;
            int         op1;
            int         op2;
            int         result;
            for (i = start ; i <= end ; i++)
            {
                copy[index++] = chars[i];
            }
            while (containsoperators(copy))
            {
                replaced = false;
                for (i = 0; i < operators.Length && !replaced ; i++)
                {
                    if ((pos = getFirst(copy, operators[i])) > 0)
                    {
                        replaced    = true;
                        op1         = getNumberBefore(copy, pos);
                        op2         = getNumberAfter(copy, pos);
                        start       = getFirstNumeric(copy, pos - 1, -1);
                        end         = getFirstNumeric(copy, pos + 1, 1);
                        result      = evaluate(op1, op2, copy[pos]);
                        copy        = replace(copy, start, end, result.ToString().ToCharArray());
                    }
                }
            }
            return readNumber(copy, 0);
        }
        private int evaluate(int op1, int op2, char op)
        {
            switch (op)
            {
                case '+':
                    return op1 + op2;
                case '-':
                    return op1 - op2;
                case '*':
                    return op1 * op2;
                case '/':
                    return (op2 != 0 ? op1 / op2 : 0);
                case '^':
                    return (int)Math.Pow((int)op1, (int)op2);
                default:
                    return 0;
            }
        }
        private int getFirstNumeric(char[] chars, int start, int delta)
        {
            if (start < 0 || start >= chars.Length || !isANumber(chars[start]))
            {
                return start - delta;
            }
            return getFirstNumeric(chars, start + delta, delta);
        }
        private bool containsoperators(char[] chars)
        {
            for (int i = 0; i < chars.Length; i++)
            {
                if (!isANumber(chars[i]))
                {
                    return true;
                }
            }
            return false;
        }
        private char[] replace(char[] chars, int start, int end, char[] replaceWidth)
        {
            char[]      result = new char[chars.Length - (end - start + 1) + replaceWidth.Length];
            int         i;
            int         index = 0;
            for (i = 0; i < start; i++)
            {
                result[index++] = chars[i];
            }
            for (i = 0; i < replaceWidth.Length; i++)
            {
                result[index++] = replaceWidth[i];
            }
            for (i = end + 1; i < chars.Length; i++)
            {
                result[index++] = chars[i];
            }
            return result;
        }
        private int getNumberBefore(char[] chars, int pos)
        {
            int result = 0;
            int factor = 1;
            pos--;
            do{
                result += (chars[pos] - '0')*factor;
                factor *= 10;
                pos--;
            }while(pos >= 0 && isANumber(chars[pos]));
            return result;
        }
        private int getNumberAfter(char[] chars, int pos)
        {
            int result = 0;
            pos++;
            do{
                result *= 10;
                result += (int)chars[pos]-'0';
                pos++;
            }while(pos < chars.Length && isANumber(chars[pos]));
            return result;
        }
        private int getFirst(char[] chars, char char2Find)
        {
            for (int i = 0; i < chars.Length; i++)
            {
                if (chars[i] == char2Find)
                {
                    return i;
                }
            }
            return -1;
        }
        private int getClosest(char[] chars, int start, int char2Find)
        {
            for (int i = start; i >= 0; i--)
            {
                if (chars[i] == char2Find)
                {
                    return i;
                }
            }
            return -1;
        }
        Boolean isANumber(char c)
        {
            return c >= '0' && c <= '9';
        }
        int readNumber(char[] letters, int index)
        {
            int result = 0;
            // letters[nextIndex] is a number, otherwise this function wouldn't be called:
            do
            {
                result *= 10;
                result += letters[index] - '0';
                index++;
            } while (index < letters.Length && isANumber(letters[index]));
            return result;
        }
        
        static void Main(string[] args)
        {
            Console.WriteLine("Formel eingeben...");
            String str = Console.ReadLine();
            char[] letters = str.ToCharArray();
            Program p = new Program();
            int result= 0;
            result = p.parse(str);
            Console.Write(result);
            Console.ReadLine();
        }
    }
}
EdiZ84 Threadstarter
Hält's aus hier
Beiträge: 15



BeitragVerfasst: Mo 15.06.09 13:57 
Hallo Leute. Wie gehts euch allen.

Ich habe jetzt endlich mal mein Parser erstellt. Jetzt ist meine große Frage wie ich die Punkt vor Strich-Rechnung einbaue. Und zwar habe ich gehört das man das Array mit den gesplitteten Teilen einfach nur sortieren müsste. Aber für andere Vorschläge bin ich immer offen. Könntet ihr mir dabei irgendwie helfen??

Danke euch im Voraus :)

Hier mein Parser:

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Parser
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //Eingabe
        private void txt_eingabe_TextChanged(object sender, EventArgs e)
        {

            try
            {   //Aufnahme der Eingabe
                var formula = txt_eingabe.Text.Trim();
                //lbl_result.Text = txt_eingabe.Text;

                //Prüfung ob Methode(checkbrackets) False/True liefert.
                if (checkBrackets(formula))
                {
                    //Bei True Aufruf der Methode(calFormulaWithBrackets)
                    lbl_result.Text = calcFormulaWithBrackets(formula).ToString();
                    //lbl_result.Text = "OK";
                }
                else
                {   //Bei False(Message)
                    lbl_result.Text = "Klammer setzung fehlerhaft";
                }

            }
            catch (Exception ex)
            {
                lbl_result.Text = ex.Message;
            }
        }

        //Klammer-Prüfung
        //Parameter(eingabe)//Rückgabe:False/True
        private bool checkBrackets(string formula)
        {
            int bracketCount = 0;

            //Schleife durchläuft die Eingabe
            foreach (var character in formula)
            {
                //Prüfung ob eine offene Klammer und geschlossenen vorhanden ist 
                if (character == '(')
                {
                    //Wert um 1 erhöhen
                    bracketCount++;
                }
                else if (character == ')')
                {
                    //Wert um 1 mindern 
                    bracketCount--;
                }
            }
            
            return (bracketCount == 0);
        }

        //Rechnen mit Klammern
        //Parameter(eingabe)Rückgabe(Zahl)
        private int calcFormulaWithBrackets(string formula)
        {
            //Tiefster Punkt der Gleichung
            int maxDepth = 0;

            //Suchlauf nach geöffneter Klammer, bei true wird Werd erhöht
            foreach (var character in formula)
            {
                if (character == '(')
                {
                    maxDepth++;
                }
            }
            //Suchlauf von hinten(
            //
            for (int formulaDepth = maxDepth; formulaDepth > 0; formulaDepth--)
            {
                bool subFormulaReplaced = false;

                do
                {   //False Setzung der Variable
                    subFormulaReplaced = false;

                    int actDepth = 0;

                    //Suchlauf nach geöffneter und geschlossener Klammer
                    //
                    for (int charIndex = 0; charIndex < formula.Length; charIndex++)
                    {
                        if (formula[charIndex] == '(')
                        {
                            actDepth++;
                        }
                        else if (formula[charIndex] == ')')
                        {
                            actDepth--;
                        }
                        //Wenn aktuelle Tiefe = FormualaTiefe
                        if (actDepth == formulaDepth)
                        {   
                            //Position der geschlossenen Klammer ermitelln
                            int closingBracketIndex = formula.IndexOf(')', charIndex);

                            //
                            var subFormula = formula.Substring(charIndex + 1, (closingBracketIndex - charIndex) - 1);

                            //Aufruf der Methode(calcFormulaWithoutBrackets)
                            var subFormulaResult = calcFormulaWithOutBrackets(subFormula);

                            formula = formula.Replace(string.Format("({0})", subFormula), subFormulaResult.ToString());

                            subFormulaReplaced = true;
                            break;
                        }
                    }
                } while (subFormulaReplaced);
            }

            return calcFormulaWithOutBrackets(formula);
        }

        //Rechnen ohne Klammern
        //Parameter(eingabe)Rückgabe(Zahl)
        private int calcFormulaWithOutBrackets(string formula)
        {
            //Über des Methodenergebnisses von der Methode(SplitFormula) an Variable
            var formulaParts = splitFormula(formula);



            //Konventierung des Wertes
            int result = int.Parse(formulaParts[0]);


            for (int index = 1; index < formulaParts.Length; index += 2)
            {
                int partValue = int.Parse(formulaParts[index + 1]);

                switch (formulaParts[index])
                {
                    case "+":
                        result = (result + partValue);
                        break;
                    case "-":
                        result = (result - partValue);
                        break;
                    case "*":
                        result = (result * partValue);
                        break;
                    case "/":
                        result = (result / partValue);
                        break;
                    default:
                        throw new Exception(string.Format("Unbekannter Operator ({0})", formulaParts[index]));
                }
            }

            return result;
        }


        //Gleichung Zerlegen
        //Parameter(eingabe)Rückgabe(String Array)
        private string[] splitFormula(string formula)
        {
            //Erstellen einer List-String Variable
            List<string> result = new List<string>();

            

            //Operatoren
            var operators = new char[] { '+', '*', '-', '/', ':' };

            //Position des letzten Operators
            int lastOperatorIndex = 0;

            string part;

            //Durchlaufen der Eingabe
            for (int index = 0; index < formula.Length; index++)
            {
                //Es wird geprüft ob die Gleichung Operatoren enthält
                if (operators.Contains(formula[index]))
                {
                    //Zerlegung und Übergabe der Zeichenfolgen 
                    part = formula.Substring(lastOperatorIndex, (index - lastOperatorIndex)).Trim();

                    //Zugriff auf die Methode(validateFormulaPart)
                    validateFormulaPart(ref part);

                    result.Add(part);
                    result.Add(formula[index].ToString());

                    lastOperatorIndex = (index + 1);
                }
            }

            part = formula.Substring(lastOperatorIndex).Trim();

            validateFormulaPart(ref part);

            result.Add(part);

            if ((result.Count % 2) == 0)
            {
                if (result[0].Length == 1 && operators.Contains(result[0][0]))
                {
                    result.Insert(0, "0");
                }
                else
                {
                    throw new Exception("Anzahl der Operatoren passt nicht");
                }
            }

            return result.ToArray();
        }

        //Parameter(formulaPart)Rückgabe(keine)
        private void validateFormulaPart(ref string formulaPart)
        {
            int intValue;

            formulaPart = formulaPart.Trim();

            if (formulaPart.Length == 0)
            {
                formulaPart = "0";
            }
          
        }

    }
}


Moderiert von user profile iconKha: C#-Tags hinzugefügt