AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Einheiten parsen

Ein Thema von Bjoerk · begonnen am 9. Mär 2015 · letzter Beitrag vom 14. Mär 2015
Antwort Antwort
Bjoerk

Registriert seit: 28. Feb 2011
Ort: Mannheim
1.384 Beiträge
 
Delphi 10.4 Sydney
 
#1

AW: Einheiten parsen

  Alt 9. Mär 2015, 20:03
Geht sehr gut. Danke Robert.

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;
  Mit Zitat antworten Zitat
Sailor

Registriert seit: 20. Jul 2008
Ort: Balaton
112 Beiträge
 
Delphi 2010 Professional
 
#2

AW: Einheiten parsen

  Alt 9. Mär 2015, 21:18
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.
  Mit Zitat antworten Zitat
Bjoerk

Registriert seit: 28. Feb 2011
Ort: Mannheim
1.384 Beiträge
 
Delphi 10.4 Sydney
 
#3

AW: Einheiten parsen

  Alt 9. Mär 2015, 21:50
Wenn du uns noch kurz sagst wo hier eine Exception auftreten kann?
  Mit Zitat antworten Zitat
Benutzerbild von BUG
BUG

Registriert seit: 4. Dez 2003
Ort: Cottbus
2.094 Beiträge
 
#4

AW: Einheiten parsen

  Alt 9. Mär 2015, 23:42
Geht sehr gut.
Ich hätte zwar versucht, die vordefinierten abgeleiteten Einheiten (TUnitStyle) ganz loszuwerden; aber schön das es dir geholfen hat

Wenn du uns noch kurz sagst wo hier eine Exception auftreten kann?
Ein Kandidat wäre das Addieren mit unterschiedlichen Einheiten: Dein Code verwirft die Einheiten dann einfach, was zu merkwürdigen Ergebnissen führen könnte.
  Mit Zitat antworten Zitat
Dejan Vu
(Gast)

n/a Beiträge
 
#5

AW: Einheiten parsen

  Alt 10. Mär 2015, 05:01
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.
Folgt diesem Weg. Der Von Bjoerk scheitert ja schon, wenn ich mal andere Einheiten nehmen will, z.B. PS, inch. Natürlich sollte man dem Evaluator (bzw. den 'Semantikroutinen') noch die Rechenregeln für Einheiten beibrigen (Basiseinheiten, Kürzen, Ersetzen: N*m/s => J etc.)

Ob dann etwas Sinnvolles herauskommt, wird sich noch zeigen müssen.
  Mit Zitat antworten Zitat
Sailor

Registriert seit: 20. Jul 2008
Ort: Balaton
112 Beiträge
 
Delphi 2010 Professional
 
#6

AW: Einheiten parsen

  Alt 10. Mär 2015, 08:22
So würde ich anfangen:
Code:
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
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.
Für das Rechnen und Umformen mit abgeleiteten Einheiten bietet sich ein endlicher Automat an, der über dem kompletten Syntaxbaum arbeitet.
  Mit Zitat antworten Zitat
Bjoerk

Registriert seit: 28. Feb 2011
Ort: Mannheim
1.384 Beiträge
 
Delphi 10.4 Sydney
 
#7

AW: Einheiten parsen

  Alt 10. Mär 2015, 10:46
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.
  Mit Zitat antworten Zitat
Bjoerk

Registriert seit: 28. Feb 2011
Ort: Mannheim
1.384 Beiträge
 
Delphi 10.4 Sydney
 
#8

AW: Einheiten parsen

  Alt 10. Mär 2015, 17:01
Das Parsen gestaltet sich jetzt doch schwieriger als angenommen.

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? (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;
  Mit Zitat antworten Zitat
Mikkey

Registriert seit: 5. Aug 2013
265 Beiträge
 
#9

AW: Einheiten parsen

  Alt 10. Mär 2015, 17:15
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.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:25 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