![]() |
Einheiten parsen
Meine User würden gerne sowas eingeben können.
5,0 kN/m2 * 0,70 m / cos(30) Die Idee war nun zwei Berechnungen durchzuführen: 1.) Die Einheiten rauslöschen und den Term herkömmlich parsen. -> Parse(5,0 * 0,70 / cos(30)) 2.) Die Term zu modifizieren und dann die Einheiten parsen. -> UnitParse([kN/m2] / [m]) Mögliche Einheiten: 'kN/m3', 'kN/m2', 'kN/m', 'kNm/m', 'kNm', 'kN', 'm3', 'm2', 'm', (Wann 'kNm/m' ebd ist und nicht kN kann ich vorher prüfen ('kNm/m' kann nur vorkommen wenn 'kNm' im Term vorhanden ist.)) Einheiten können nur in + - * / Operationen vorkommen. Doch wie berechnet man das? Was nehm ich da für einen Typ? Kann man da irgendwie bitweise addieren? Hat da jemand eine Idee? Vom Ergebnis muß ja auch wieder auf die Einheit geschlossen werden können (MyTypToStr)? |
AW: Einheiten parsen
Du musst dir das einfach nur anders vorstellen:
Code:
entspricht eben
5,0 kN/m2 * 0,7 m
Code:
Die Einheiten sind somit eigenständige Werte und müssen auch also solche behandelt werden.
5,0 * (kN) / ( (m)*(m) ) * 0,7 * (m)
Am Besten du erstellst dir so einen Einheiten-Typ (record) und einen EinheitWert-Typ (record) - ein Zahlwert und eine Einheit - und definierst die Rechenregeln. |
AW: Einheiten parsen
Zitat:
|
AW: Einheiten parsen
Zitat:
Code:
5,0 * 0,7 (kN) / ( (m)*(m) ) * (m)
Code:
5,0 * 0,7 (kN) * (m) / ( (m)*(m) )
Code:
Gruß
5,0 * 0,7 (kN) / (m)
K-H |
AW: Einheiten parsen
Cool, sowas willst Du programmieren...
Ich verstehe das so, dass Du als Ergebnis eine Zahl hast und die Einheit dieser Zahl. Ich würde auch die Einheiten von den Zahlenwerten trennen und dann in einem separaten Parser durchrechnen. Sowas wie "k" wie Kilo oder "M" wie Mega würde ich aber in der Zahlenberechnung lassen, da das ja eigentlich nur eine andere Exponentialschreibweise ist. Und pass auf, dass das noch hinten heraus nicht unheimlich kompliziert wird. |
AW: Einheiten parsen
Ich glaub das Trennen ist nicht ganz einfach, besser in einem Rutsch?
Sir Rufo, meinst du so?
Delphi-Quellcode:
type
TParserUnitStyle = (pusKNdivM3, pusKNdivM2, pusKNdivM, pusKNMdivM, pusKNM, pusKN, pusM3, pusM2, pusM); TParserUnit = record private FValue: TParserUnitStyle; public function GetPlus(const A, B: TParserUnit):TParserUnitStyle; function GetMinus(const A, B: TParserUnit):TParserUnitStyle; function GetDiv(const A, B: TParserUnit):TParserUnitStyle; function GetMult(const A, B: TParserUnit):TParserUnitStyle; property Value: TParserUnitStyle read FValue write FValue; end; TParserFloat = record Value: double; UnitValue: TParserUnit; end; |
AW: Einheiten parsen
Ich würde mir so etwas wie "atomare" Einheiten definieren: m und kN.
Dann kann man sich zusammengesetzte Einheiten definieren:
Delphi-Quellcode:
Der Inhalt des Arrays gibt dann den Exponenten der Einheit an. Zum Beispiel: m entspricht {1, 0}; kN/m entspricht {-1, 1}
type = record
units: array[m..kN]of integer; end; Dann kann man sich Operationen auf den Zusammengesetzten Einheiten definieren: Multiplikation/Division entspricht der Addition/Substraktion der Arrays. Addition/Subtraktion entspricht einem Test auf Gleichheit der Arrays; bei Ungleichheit könnte man eine Exception werfen. Aber ehrlich gesagt würde ich die Werte gleich mit rein packen:
Delphi-Quellcode:
type = record
value: double; units: array[m..kN]of integer; end; |
AW: Einheiten parsen
Ja. In etwa so war auch mein erster Ansaz. Das wollte ich aber umgehen.
Delphi-Quellcode:
constructor TUnitParser.Create;
begin inherited Create; FM := 1000.0; FKN := -1.0; SetUnit(0, 'R', 'kN/m3', FKN / FM / FM / FM); SetUnit(1, 'S', 'kN/m2', FKN / FM / FM); SetUnit(2, 'T', 'kN/m', FKN / FM); SetUnit(3, 'U', 'kNm/m', FKN); SetUnit(4, 'V', 'kNm', FKN * FM); SetUnit(5, 'W', 'kN', FKN); SetUnit(6, 'X', 'm3', FM * FM * FM); SetUnit(7, 'Y', 'm2', FM * FM); SetUnit(8, 'Z', 'm', FM); end; Kann man nicht irgendwie mit eigenen Typen rechnen? Wäre mir lieber.:gruebel: |
AW: Einheiten parsen
@BUG
Du meinst Grundeinheiten?: m, N, kg, s, ... @Bjoerk Hast Du ![]() @TUnitParser Was machst Du, wenn jemand mit MN oder cm um die Ecke kommt. |
AW: Einheiten parsen
Ja, genau das. Wußte gar nicht daß D2007 das schon hat. Probiers mal aus und melde mich nochmal. Thanx.
|
AW: Einheiten parsen
Zitat:
![]() Im Prinzip könnte man so ein Einheitensystem auch anwendungspezifisch festlegen, aber es ist vermutlich clever SI-Einheiten zu benutzen. Die "zusammengesetzt Einheiten" sind nach der Terminologie dann abgeleite Einheiten. |
AW: Einheiten parsen
Ja, man kommt dann nicht drumherum diese Basiseinheiten mit allen ihren Ausprägungen (m, cm => 1/100m, km => 1000m, s, min => 60s, h => 3600s, ...) als Typ zu definieren.
Dann benötigt man einen weiteren Typen, der mit den zusammengesetzten Basiseinheiten umgehen kann. Da hat man dann Basis-Einheiten im Zähler und im Nenner, die man dann auch wegkürzen kann, damit aus
Code:
werden kann.
60 Meter/Sekunde * 60 Sekunden = 3600 Meter
Code:
Spannend wird es dann eben noch, wenn man unterschiedliche Einheiten aus der gleichen Familie hat:
Wert1 => Wert: 60, Einheit Zähler: [Meter], Einheit Nenner: [Sekunde]
Wert2 => Wert: 60, Einheit Zähler: [Sekunde], Einheit Nenner: [] Ergebnis => Wert 3600, Einheit Zähler: [Meter], Einheit Nenner: []
Code:
Wert1 => Wert: 60, Einheit Zähler: [KilometerMeter], Einheit Nenner: [Stunde]
Wert2 => Wert: 60, Einheit Zähler: [Sekunde], Einheit Nenner: [] Ergebnis => Wert 1, Einheit Zähler: [KiloMeter], Einheit Nenner: [] |
AW: Einheiten parsen
Zitat:
Beim Einlesen werden alle Werte und Einheiten zu den Basiseinheiten normalisiert. Beim Ausgeben muss man gucken, wie die Anforderungen sind. |
AW: Einheiten parsen
Kann man da nicht mit Sets oder irgendwie bitweise was rechen, sonst tippt man sich ja den Wolf? (und sind hier gerade mal 9 mögliche Kombinationen). Die usNone müssen natürlich noch ausgefüllt werden.
Delphi-Quellcode:
type TUnitStyle = (usNone, usKNdivM3, usKNdivM2, usKNdivM, usKNMdivM, usKNM, usKN, usM3, usM2, usM); function GetUnitPlus(const A, B: TUnitStyle): TUnitStyle; begin if A = B then Result := A else Result := usNone; end; function GetUnitMinus(const A, B: TUnitStyle): TUnitStyle; begin if A = B then Result := A else Result := usNone; end; function GetUnitDiv(const A, B: TUnitStyle): TUnitStyle; begin Result := usNone; case A of usKNdivM3: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNdivM2: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNdivM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNMdivM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKN: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM3: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM2: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; end; end; function GetUnitMult(const A, B: TUnitStyle): TUnitStyle; begin Result := usNone; case A of usKNdivM3: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNdivM2: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNdivM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNMdivM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKNM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usKN: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM3: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM2: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; usM: begin case B of usKNdivM3: Result := usNone; usKNdivM2: Result := usNone; usKNdivM: Result := usNone; usKNMdivM: Result := usNone; usKNM: Result := usNone; usKN: Result := usNone; usM3: Result := usNone; usM2: Result := usNone; usM: Result := usNone; end; end; end; end; end. |
AW: Einheiten parsen
Zitat:
Zum Beispiel mit den Array aus Beitrag ![]() EDIT: Hast du das Ganze per Hand heruntergeschrieben? :pale: |
AW: Einheiten parsen
Zitat:
Deinen Ansatz #9 hab ich nich kapiert. Montags kapier ich noch weniger als sonst.. Kannst nochmal etwas erläutern? |
AW: Einheiten parsen
Deine Basiseinheiten definierst du weiter als Aufzählungstyp:
Delphi-Quellcode:
Die zusammengesetzten Einheiten lassen sich auch in Exponentenschreibweise aufschreiben. Die Exponenten kannst du dann einfach in einem Array speichern.
type
TFundamentalUnit = (fuKN, fuM);
Delphi-Quellcode:
type
TDerivedUnit = array[fuKN..fuM] of Integer.
Zum Beispiel die Multiplikation kann man nun ganz einfach auf eine Addition der Array-Einträge abbilden (quasi mit den Rechenregeln für Potenzen):
Ich hoffe das ist verständlicher :wink: |
AW: Einheiten parsen
Geht sehr gut. Danke Robert. :thumb:
Delphi-Quellcode:
type
TUnitBase = (fuKN, fuM); TUnitStyle = (usNone, usKNdivM3, usKNdivM2, usKNdivM, usKNM, usKN, usM3, usM2, usM); TUnits = class private FItems: array[usNone..usM, fuKN..fuM] of integer; function GetValues(Index: TUnitStyle): double; function GetAsString(Index: TUnitStyle): string; public property Values[Index: TUnitStyle]: double read GetValues; property AsString[Index: TUnitStyle]: string read GetAsString; function GetPlus(const A, B: TUnitStyle): TUnitStyle; function GetMinus(const A, B: TUnitStyle): TUnitStyle; function GetDiv(const A, B: TUnitStyle): TUnitStyle; function GetMult(const A, B: TUnitStyle): TUnitStyle; constructor Create; end; var Form1: TForm1; implementation {$R *.dfm} { TUnits } constructor TUnits.Create; begin FItems[usNone, fuKN] := 0; FItems[usNone, fuM] := 0; FItems[usKNdivM3, fuKN] := 1; FItems[usKNdivM3, fuM] := -3; FItems[usKNdivM2, fuKN] := 1; FItems[usKNdivM2, fuM] := -2; FItems[usKNdivM, fuKN] := 1; FItems[usKNdivM, fuM] := -1; FItems[usKNM, fuKN] := 1; FItems[usKNM, fuM] := 1; FItems[usKN, fuKN] := 1; FItems[usKN, fuM] := 0; FItems[usM3, fuKN] := 0; FItems[usM3, fuM] := 3; FItems[usM2, fuKN] := 0; FItems[usM2, fuM] := 2; FItems[usM, fuKN] := 0; FItems[usM, fuM] := 1; end; function TUnits.GetValues(Index: TUnitStyle): double; begin Result := IntPower(FItems[Index, fuKN], FItems[Index, fuM]); end; function TUnits.GetAsString(Index: TUnitStyle): string; begin case Index of usKNdivM3: Result := 'kN/m3'; usKNdivM2: Result := 'kN/m2'; usKNdivM: Result := 'kN/m'; usKNM: Result := 'kNm'; usKN: Result := 'kN'; usM3: Result := 'm3'; usM2: Result := 'm2'; usM: Result := 'm'; else Result := ''; end; end; function TUnits.GetPlus(const A, B: TUnitStyle): TUnitStyle; begin if A = B then Result := A else Result := usNone; end; function TUnits.GetMinus(const A, B: TUnitStyle): TUnitStyle; begin if A = B then Result := A else Result := usNone; end; function TUnits.GetMult(const A, B: TUnitStyle): TUnitStyle; var I: TUnitStyle; KN, M: integer; begin Result := usNone; KN := FItems[A, fuKN] + FItems[B, fuKN]; M := FItems[A, fuM] + FItems[B, fuM]; for I := Low(TUnitStyle) to High(TUnitStyle) do if (KN = FItems[I, fuKN]) and (M = FItems[I, fuM]) then begin Result := I; Break; end; end; function TUnits.GetDiv(const A, B: TUnitStyle): TUnitStyle; var I: TUnitStyle; KN, M: integer; begin Result := usNone; KN := FItems[A, fuKN] - FItems[B, fuKN]; M := FItems[A, fuM] - FItems[B, fuM]; for I := Low(TUnitStyle) to High(TUnitStyle) do if (KN = FItems[I, fuKN]) and (M = FItems[I, fuM]) then begin Result := I; Break; end; end; { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var Units: TUnits; A, B, C: TUnitStyle; begin Units := TUnits.Create; try A := usM3; B := usKNdivM2; C := Units.GetMult(A, B); ShowMessage(Units.AsString[C]); A := usKN; B := usM; C := Units.GetDiv(A, B); ShowMessage(Units.AsString[C]); finally Units.Free; end; end; |
AW: Einheiten parsen
Und warum nicht einen stinknormalen Expression-Parser um Einheiten erweitern und die Umrechnung in die Semantikroutinen legen? Hätte auch noch den Vorteil einer komfortablen Fehlerbehandlung.
|
AW: Einheiten parsen
Wenn du uns noch kurz sagst wo hier eine Exception auftreten kann? :wink:
|
AW: Einheiten parsen
Zitat:
Zitat:
|
AW: Einheiten parsen
Zitat:
Ob dann etwas Sinnvolles herauskommt, wird sich noch zeigen müssen. |
AW: Einheiten parsen
So würde ich anfangen:
Code:
Compatible ist ein 2-dimensionales Feld ARRAY[TUnit,TUnit] OF Boolean, der Eintrag [cm,m] wird auf True gesetzt, [g,cm] auf False. Normalize ist ein ARRAY[TUnit,TUnit] OF Real und enthält die Umrechnungsfaktoren in die Darstellungeinheit: [cm,m] = 0.01,[kg,g] = 1000. Die Eingabe "30 knirsch" wird von der kontextfreien Fehlerbehandlung abgewiesen, die Eingabe "30g + 40 cm" von der kontext-sensitiven. Die Hinzunahme weiterer Einheiten bedeutet eine zusätzliche Zeile in der Grammatik und zwei Änderungen in Compatible und Normalize.
Expr -> Left_Expr '+' Right_Expr
=> "IF Compatible($Left_Expr.Unit,$Right_Expr.Unit)= True THEN $Expr := Normalize($Left_Expr.Value) + Normalize($Right_Expr.Value) ELSE Error(UnitsNotCompatible)" | Factor Unit Factor -> <Number> | '(' Expression ')' Unit -> | m | cm | g | kg Für das Rechnen und Umformen mit abgeleiteten Einheiten bietet sich ein endlicher Automat an, der über dem kompletten Syntaxbaum arbeitet. |
AW: Einheiten parsen
Hab ich ja schon gesagt daß ich keinen Extra Durchgang mache sondern daß die Einheitn im Parserprozess mitlaufen. Ich brauch auch wirklich nur die angegeben Einheiten. Und daß bei syntaktisch falschen Operationen (z.B. m + kN) nicht irgendwas rauskommt sondern keine Einheit angezeigt wird fängt der Parser nun ab.
|
AW: Einheiten parsen
Das Parsen gestaltet sich jetzt doch schwieriger als angenommen. :oops:
Der Term sieht intern so aus. In eckigen Klammern die Einheiten. Die Einheiten werden durch einfache Token ersetzt. 5,0[D]*0,70[K]/cos(30) Das das soll jetzt hier durchgejagt werden. Hab keinen Plan wie ich das hier anstellen soll?:gruebel: (Getrenntes Parsen von Einheitn und Term ist m.E. aus syntaktischen Gründen nicht möglich.) Im Beispiel landet der Parser irgendwann in GetPlus. Er muß aber auch mal irengdwann in FUnit.GetPlus landen (Der Parser hat ein Feld der Klasse TUntis (Siehe #18)).
Delphi-Quellcode:
// Die eigentliche Auflösung des Terms;
// *** Die Rekursion arbeitet rückwärts; // "~" (unäres Minus) immer zuerst prüfen ; // Sqrt vor Sqr, ArcXXX vor XXX; // Entweder LastPos und true oder FirstPos und false; // LastPos und true: "+", "-", "*", "/", ":", "\", (":" = div, "\" = mod); // FirstPos und false: "^", Functions; function TJMUnitParser.Solve(S: string; var Style: TUnitStyle): double; begin try if LastPos('+', S) > 0 then Result := GetPlus(Solve(Left(S, '+', true), Style), Solve(Right(S, '+', true), Style)) else ... Weitere .. else if Parenthesis(S, true) then // "~( .. )" Result := -Solve(S, Style) else if Parenthesis(S, false) then // "( .. )" Result := Solve(S, Style) else Result := StrToFloat(MyStringReplace(S, '~', '-', false, false)); except Result := 0; end; end; // Gibt den linken Teil einer Operation zurück; function TJMUnitParser.Left(const S, Substr: string; const Last: boolean): string; begin if Last then // Den Term von rechts nach links durchsuchen; Result := StrLeft(S, LastPos(Substr, S) - 1) else // Den Term von links nach rechts durchsuchen; Result := StrLeft(S, FirstPos(Substr, S) - 1); end; // Gibt den rechten Teil einer Operation zurück; function TJMUnitParser.Right(const S, Substr: string; const Last: boolean): string; begin if Last then // Den Term von rechts nach links durchsuchen; Result := StrRight(S, LastPos(Substr, S) + Length(Substr)) else // Den Term von links nach rechts durchsuchen; Result := StrRight(S, FirstPos(Substr, S) + Length(Substr)); end; function TJMUnitParser.GetPlus(const X, Y: double): double; begin Result := X + Y; end; |
AW: Einheiten parsen
Ich würde es für sinnvoller und wesentlich einfacher halten, wenn Du Dir einen fertigen Parser nimmst, und diesen entsprechend nach Deinen Bedürfnissen anpasst.
- Leerzeichen werden ignoriert - Du hast Zahlen, musst das Dezimalkomma ensprechend behandeln. - wenn auf eine Ziffer ein Buchstabe folgt, handelt es sich um eine Einheit, implizit wird eine Multiplikation eingefügt; die Einheit wird gegen den Satz von Einheiten geprüft - wenn auf einen Buchstaben eine Klammer auf folgt, ist es ein Funktionsaufruf, das Token wird dann gegen den Satz der verfügbaren Funktionen geprüft - Klammerung sollte der Standardparser bereits beherrschen. Es ist generell keine gute Idee, in den Parser gleich die Interpretation des Ausdrucks einzubauen. Stattdessen kannst Du den gepars-ten Ausdruck auswerten und weißt dabei vorher, dass Du keinen Syntaxfehler hast. |
AW: Einheiten parsen
Hallo Mikkey, das ist ein fertiger Parser. Und implizit kann man da m.E. nicht einfach mal eine Multiplikation einführen? Mir gehts hier nur um das Handling der Rekursion (Wie kommt man die Einheit ran?)
|
AW: Einheiten parsen
Eine Konstante ist nicht nur einfach eine Zahl, sondern entweder ein Faktor oder ein physikalischer Wert.
Code:
PS: Wenn du die Frage nach Rekursivität stellst, dann hast Du nicht wirklich einen Parser. Oder Du hast einen, weißt aber nicht, wie man ihn verwendet. Oder Du hast einen, weißt, wie man ihn verwendet, hast aber einfach die falsche Frage gestellt. ;-)
NumericConstant ::= Factor | PhysicalValue
Factor ::= AnyNumber PhysicalValue ::= AnyNumber [Unit] |
AW: Einheiten parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Bud,
ja, die Rekursion hatte ich gestern nicht wirklich (mehr) kapiert gehabt. Aber heute ist ein neuer Tag. So wie ich's vorhatte ging's aus mehreren Gründen nicht. Man kann entweder mit Äpfeln (double) mit oder Birnen (TEinheiten) rechnen. Also dann doch einen Extradurchlauf. Man braucht eine neue Solve.
Delphi-Quellcode:
Vom Ablauf her isses hier so: Im obigen Beispiel 5,0 kN/m2 * 0,70 m / cos(30) wird beim Aufbereiten für's Einheitenparsen zunächst [Einheit_1][Einheit_ kN/m2] * [Einheit_m] / [Einheit_1]. Um den Term Term mathematisch so zu parsen wie den Term ohne Einheiten braucht muß man nur [Einheit_1][Einheit_ X] zu [Einheit_ X] ersetzen (bzw. [Einheit_ X][Einheit_1] zu [Einheit_ X]).
function TJMUnitParser.Solve(S: string): TParserUnitStyle;
begin try if LastPos('+', S) > 0 then Result := GetPlus(Solve(Left(S, '+', true)), Solve(Right(S, '+', true))) else if LastPos('-', S) > 0 then Result := GetMinus(Solve(Left(S, '-', true)), Solve(Right(S, '-', true))) else if LastPos('*', S) > 0 then Result := GetMult(Solve(Left(S, '*', true)), Solve(Right(S, '*', true))) else if LastPos('/', S) > 0 then Result := GetDiv(Solve(Left(S, '/', true)), Solve(Right(S, '/', true))) else if LastPos(':', S) > 0 then // div; Result := GetIntDiv(Solve(Left(S, ':', true)), Solve(Right(S, ':', true))) else if LastPos('\', S) > 0 then // mod; Result := GetIntMod(Solve(Left(S, '\', true)), Solve(Right(S, '\', true))) else if FirstPos('^', S) > 0 then Result := GetPower(Solve(Left(S, '^', false)), Solve(Right(S, '^', false))) else if Parenthesis(S) then // ( .. ) Result := Solve(S) else Result := FUnits.SignToStyle(S); except Result := pusNone; end; end; Hab den Code mal angehängt. Falls du Böcke hat, eine Variante mit einem Stack wär' nicht schlecht. :-D LG Thomas |
AW: Einheiten parsen
Hmm. Du solltest deinen Parser so schreiben, wie man eine Grammatik schreibt.
D.h. 1. Du baust dir einen Tokenizer/Lexer, der dir aus deinem String die entsprechenden Token (Operator, Klammer, Zahl, Einheit) extrahiert. 2. Dann schreibst Du den Parser. Leider hab ich gerade keinen da, weil ich unterwegs bin und mein Privatlaptop mit einem fertigen Parser zuhause ist. Im Prinzip geht es so:
Delphi-Quellcode:
'IsZahl' liefert true, wenn das nächste Token eine Zahl ist. außerdem wird in 'leftNumber' dann die Zahl geliefert.
// Term ::= Zahl Operator Zahl | '(' Term ')'
function IsTerm (var term : TParserNode) : Boolean; Var leftNumber, operator, rightNumber : TToken; begin result := false if IsOpenBracket then begin if IsTerm (term) then if IsClosingBracket then exit(true); SyntaxError('Term expected'); end else if IsZahl(leftNumber) then if IsOperator(operator) then If IsZahl(rightNumber) then begin term := TTermNode.Create (leftNumber, operator, rightNumber) result := True; end; // falls result=false ist, müsstest du hier noch aufräumen, oder mit interfaces arbeiten end; Das gleiche gilt für 'IsOperator'. 'IsTerm' macht im Prinzip genau das Gleiche: Wenn es die Tokensequenz einen Term ergibt, liefert die Funktion 'True' und den Term. Wichtig ist, das man beim Erkennen eines Tokens zum nächsten geht. Manchmal muss man sich das aktuelle Token, sondern das darauf folgende Token anschauen, um zu entscheiden, was zu tun ist (look ahead). |
AW: Einheiten parsen
Ja, das weiß ich doch selbst? Dieser Parser arbeitet aber halt anders. Ansonsten ja zum Beispiel so, so einen hab ich auch mal gemacht.
Delphi-Quellcode:
procedure TParser.ParseFunctions(var S: string);
var Index: integer; begin Index := OperatorPos(S, 'A'); while Index > 0 do begin MathWork(S, Index); Index := OperatorPos(S, 'A'); end; end; procedure TParser.ParseOperator(var S: string; const AOperator: Char); var Index: integer; begin Index := OperatorPos(S, AOperator); while Index > 0 do begin MathWork(S, Index); Index := OperatorPos(S, AOperator); end; end; procedure TParser.Parse(var S: string); begin // Diese Reihenfolge ParseFunctions(S); // Funktionen, Von Rechts nach Links; ParseOperator(S, '^'); // Power, Von Rechts nach Links; ParseOperator(S, '*'); // Punktrechnung, Von Links nach Rechts; ParseOperator(S, '+'); // Strichrechnung Von Links nach Rechts; end; function TParser.Solve(T: string; Sender: boolean): string; // Aufruf mit Sender = false; var A, B: Integer; S: string; AFloat: double; begin while HaveBrackets(T, A, B) do begin S := StrMid(T, A + 1, B - 1); Solve(S, true); Parse(S); T := StrLeft(T, A - 1) + S + StrRight(T, B + 1); end; if not Sender then begin Parse(T); if TryStrToFloat(GetNumber(T), AFloat) then Result := GetNumber(T) else Result := '0'; // Syntaxerror end; end; |
AW: Einheiten parsen
Was ich aber nicht hinkrieg ist daraus einen Stack zu machen? :oops:
Delphi-Quellcode:
In meinem jugendlichen Leichtsinn hab ich mal dahin überlegt? Geht das so in der Richtung? :gruebel:
function TJMUnitParser.Solve(S: string): TParserUnitStyle;
begin try if LastPos('+', S) > 0 then Result := FUnits.GetAdd(Solve(Left(S, '+', true)), Solve(Right(S, '+', true))) else if LastPos('/', S) > 0 then Result := FUnits.GetMult(Solve(Left(S, '/', true)), Solve(Right(S, '/', true))) else if LastPos('*', S) > 0 then Result := FUnits.GetDiv(Solve(Left(S, '*', true)), Solve(Right(S, '*', true))) else if FirstPos('^', S) > 0 then Result := FUnits.GetPower(Solve(Left(S, '^', false)), Solve(Right(S, '^', false))) else if Parenthesis(S) then Result := Solve(S) else Result := FUnits.SignToStyle(S); except Result := pusNone; end; end;
Delphi-Quellcode:
function TJMUnitParser.Solve(const Term: string): TParserUnitStyle;
var Stack: TParserStack; begin Result := pusNone; Stack := TParserStack.Create; try Stack.Push(Term); while not Stack.Empty do begin S := Stack.Pop.S; if LastPos('+', S) > 0 then begin Result := Stack.Push(S); end else if LastPos('/', S) > 0 then begin Result := Stack.Push(S); end else if LastPos('*', S) > 0 then begin Result := Stack.Push(S); end else if Parenthesis(S) then begin Result := Stack.Push(S); end else Result := FUnits.SignToStyle(S); end; finally Stack.Free; end; end; |
AW: Einheiten parsen
Ein Parser wird einen Baum erzeugen (wenn er das Ergebnis nicht schon zur Parsezeit ausrechnet). Mir fehlt bei deinem Parser einfach nur der Lexer. Ich würde das einfach nicht alles zusammen wurschteln, das ist alles.
|
AW: Einheiten parsen
Der Witz an diesem Parser ist ja daß er keinen Tokenizer braucht. Durch z.B.
Delphi-Quellcode:
wird das Token + gezogen. Left ist der Tel links vom Operator, Right rechts davon.
(Solve(Left(S, '+')), Solve(Right(S, '+')))
Ich hab echt keinen Plan wie ich das hier machen kann? Hab nur einmal einen Stack ala Floodfill gemacht. |
AW: Einheiten parsen
Na ja. Man braucht wirklich keinen modularen Aufbau und kann alles in eine Klasse stecken, ist schon klar. Irgendwo hier ist auch Code für einen Parser der mit 50 Zeilen Code auskommt.
|
AW: Einheiten parsen
Ich lass es rekursiv. Die Wahrscheinlichkeit eines Stackoverflow ist m.E. ziemlich gering.
Der Stack sieht z.B. so aus. Das ist keine UPN. 1+3+5 Result1 = 1+3+5 = Result3 + Result4 + Result5 Result2 = 1+3 (Left) = Result3 + Result4 Result3 = 1 (Left) Result4 = 3 (Right) Result5 = 5 (Right) Result = Result1 |
AW: Einheiten parsen
Zitat:
Delphi-Quellcode:
unit uSimpleParser; // (C) 2011, Dr. Joachim Mohr, Dipl.-Ing. Thomas Abel;
interface uses SysUtils, StrUtils, Math; function TermToFloat(S: string): double; implementation function OperatorPos(const Substr, S: string): integer; var I, N: integer; begin Result := 0; N := 0; for I := Length(S) downto 1 do begin if S[I] = '(' then Inc(N); if S[I] = ')' then Dec(N); if N = 0 then if PosEx(Substr, S, I) = I then begin Result := I; Break; end; end; end; function StrMid(const S: string; const A, B: integer): string; begin Result := Copy(S, A, B - A + 1) end; function StrLeft(const S: string; const B: integer): string; begin Result := StrMid(S, 1, B); end; function StrRight(const S: string; const A: integer): string; begin Result := StrMid(S, A, Length(S)); end; function Left(const S, Substr: string): string; begin Result := StrLeft(S, OperatorPos(Substr, S) - 1); end; function Right(const S, Substr: string): string; begin Result := StrRight(S, OperatorPos(Substr, S) + Length(Substr)); end; function Parenthesis(var S: string; const Minus: boolean): boolean; begin Result := false; if Minus then begin if Length(S) > 3 then if Copy(S, 1, 2) = '~(' then begin S := StrMid(S, 3, Length(S) - 1); Result := true; end; end else begin if Length(S) > 2 then if S[1] = '(' then begin S := StrMid(S, 2, Length(S) - 1); Result := true; end; end; end; function Solve(S: string): double; begin try if OperatorPos('+', S) > 0 then Result := Solve(Left(S, '+')) + Solve(Right(S, '+')) else if OperatorPos('-', S) > 0 then Result := Solve(Left(S, '-')) - Solve(Right(S, '-')) else if OperatorPos('*', S) > 0 then Result := Solve(Left(S, '*')) * Solve(Right(S, '*')) else if OperatorPos('/', S) > 0 then Result := Solve(Left(S, '/')) / Solve(Right(S, '/')) else if Parenthesis(S, true) then Result := -Solve(S) else if Parenthesis(S, false) then Result := Solve(S) else Result := StrToFloat(StringReplace(S, '~', '-', [])); except Result := 0; end; end; function TermToFloat(S: string): double; // ~ für unäres Minus; begin Result := 0; S := StringReplace(S, #32, '', [rfReplaceAll]); if Length(S) > 0 then begin if S[1] = '-' then S[1] := '~'; if S[1] = '+' then Delete(S, 1, 1); S := StringReplace(S, '*+', '*', [rfReplaceAll]); S := StringReplace(S, '/+', '/', [rfReplaceAll]); S := StringReplace(S, '(+', '(', [rfReplaceAll]); S := StringReplace(S, '*-', '*~', [rfReplaceAll]); S := StringReplace(S, '/-', '/~', [rfReplaceAll]); S := StringReplace(S, '(-', '(~', [rfReplaceAll]); Result := Solve(S); end; end; end. |
AW: Einheiten parsen
Liste der Anhänge anzeigen (Anzahl: 1)
Es ist kein simples Problem, jedenfalls, wenn man es allgemeiner halten will. Ich habe mich aus Interesse mal daran versucht, im Anhang gibt es dazu ein kleines, bei weitem nicht perfektes Programm. Für eine generelle Lösung müßte man natürlich einen Einheitenparser gemäß SI implementieren.
|
AW: Einheiten parsen
Hallo Sailor,
konnte deinen Code leider nicht testen weil ich keine Generics hab. Was ich bei Tests mit meinen Parser jedoch festgestellt habe, daß im Verlauf des Parsens Einheiten entstehen können die nicht existieren, z.B. bei 1 kN/m3 / 1 m3 * 1 m3 = 1 kN/m6 * 1 m3 = 1 kN/m3. Deshalb mache ich zwei Durchläufe, einmal / vor * und einmal * vor /. |
AW: Einheiten parsen
Die Generics sind nur der Beqemlichkeit geschuldet, man kann die üblichen arithmetischen Operatoren beibehalten. Ändere die generischen Operatoren (+,-,*,/) einfach in Funktionsaufrufe. d.h. da, wo in der Unit Actions "Normalize(Pop)+Normalize(Pop)" steht, machst Du "Add(Normalize(Pop),Normalize(Pop))" draus und änderst "CLASS OPERATOR Add..." in der Unit ExpStack zu "FUNCTION Add...". In der Unit Actions müßten bei "/" und "-" eigentlich die beiden oberen Stackelemente vertauscht werden. Ich habe aber gelernt, daß zumindest in D2010 die Parameter von rechts nach links ausgewertet werden. Na ja, Funktionen mit Seiteneffekten sind eben des Teufels. Ansonsten ist deine Aufgabe nicht ohne. Das eigentliche Problem ist die Synchronität von numerischen Ausdrücken und Einheitenausdrücken, ansonsten kann da viel Unsinn passieren. In einer verallgemeinerten Variante würde ich eine Arithmetik über den Einheiten aufbauen. Richtig fies wird es aber erst, wenn Variable zugelassen sind. Dann gibt es so schöne Dinge wie "3 kN/m * 4 kN / 3 m", eine echte Herausforderung :twisted:. Und wie gesagt, in meiner Variante gibt es sicher auch noch Überraschungen, ich hatte keine Zeit, das Ding ausgiebig zu testen.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:07 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz