Formeln sinnvoll parsen

  • VB.NET

Es gibt 17 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Formeln sinnvoll parsen

    Nabend,

    bin gerade dabei mir zu überlegen wie man am besten Funktionen parsen kann.
    Nur ersteckt sich das ganze im Moment irgendwie ins endlose.

    Ich hab es zuerst mit Substring, Split, usw. versucht, doch wird das ganze etwas viel zu lang.
    Lese mich gerade mal wieder in RegEx ein (hab dazu wieder alles vergessen :P).

    Naja zur frage, wie würdet ihr sinnvoll Funktionen parsen?
    Mit Funktionen sind noch erstmal die einfachen gemeint (Lineare; Quadratische, Ganzrationale 3. Grad aufwärts):

    f(x) = mx + b
    f(x)= a*(x-xs)+ys
    f(x)=ax^3+bx^2+cx+d
    f(x)=ax^5+cx^3+dx2+e

    Nur irgendwie finde ich keinen einheitlichen Weg, im Moment müsste ich für jede Funktion einen Sub schreiben.
    Und das ganze summiert sich auf vieeel zu vieeeeel Zeilen..

    Kennt ihr da eine bessere Lösung/Idee/Funfktion/.dll/sonstwas :P ?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Eistee“ ()

    Meinst Du mit parsen so:

    VB.NET-Quellcode

    1. Function f1(ByVal x As Double, ByVal m As Double, ByVal b As Double) As Double
    2. Return m * x + b
    3. End Function
    4. Function f2(ByVal x As Double, ByVal a As Double, ByVal s As Double, ByVal y As Double) As Double
    5. Return a * (x - x * s) + y * s
    6. End Function
    7. Function f3(ByVal x As Double, ByVal a As Double, ByVal b As Double, ByVal c As Double, ByVal d As Double) As Double
    8. Return a * x ^ 3 + b * x ^ 2 + c * x + d
    9. End Function
    10. Function f4(ByVal x As Double, ByVal a As Double, ByVal c As Double, ByVal d As Double, ByVal e As Double) As Double
    11. Return a * x ^ 5 + c * x ^ 3 + d * x ^ 2 + e
    12. End Function

    Oder dass man nur den String übergibt und das Programm erkennt selbst was gerechnet werden muss?
    Für letztere Variante gibt es eine Lib hier im Showroom. ExpressionParser oder so hieß die.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Eigentlich bäuchte ich alles einzelt, da der komplette Rechenweg in kleinsten schritten mit ausgegeben werden soll.
    Wie die Erklärungen auf Wikipedia:







    Quelle: Wikipedia.org - Quadratische Funktion Scheitelpunktbestimmung

    Dafür müsste ich alle einzelnen Teile haben, die Variablen m, x, b, a, c, d, ..., die Exponenten und die Operatoren je nach Funktion.
    Da ist der Parser von Quadsoft ehr nicht hilfreich.

    Ich glaube wohl, ich komm um den RegEx Berg nicht rum. :wacko:

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Eistee“ ()

    Eistee schrieb:

    RegEx Berg

    Vergiss es. Erstelle dir eine Grammatik, die alle Polynome, wie du sie meinst, produzieren kann. Vielleicht findest du auch was im Netz. Die Frage ist, ob du Ausdrücke vereinfachen oder auswerten willst. Auswerten ist einfach, Vereinfachen ist schwer. Dafür gibts nämlich Algebra-Programme. Eventuell kommst du um ein bisschen theoretische Informatik nicht drumherum. Siehe dazu Wikipedia: Formale Grammatik.

    Die Grammatik für Ausdrücke aus Zahlen und Operatoren könnte so aussehen:
    G = (N, T, P, S) mit N = {ZAHL, OP}, T = {Ganzzahlen, +, -}, P = {
    S --> ZAHL | ZAHL OP S,
    OP --> + | - }
    und S = S (Startsymbol).

    Das kannst du erweitern, in deinem Fall um Klammern, Potenzen, Variablen. Einen gegebenen Ausdruck kannst du mit dieser Grammatik in einen Syntaxbaum zerlegen. Beim Auswerten wars das schon. Zum Vereinfachen müsste man sich noch was überlegen.
    Gruß
    hal2000
    Ach so war das gemeint.
    Ja, das wird ein ganzer Haufen Code werden.

    Du müsstest etwas machen, was Zeichen für Zeichen einließt und verarbeitet. Dabei sollte alles so weit wie möglich abstrahiert werden.
    Versuche in ganz kleinen Schritten zu denken und die im Kopf in Programmlogik umzusetzen.
    Ich hätte da an sowas gedacht:
    Es wird zeichenweise der Eingabestring überprüft und immer neue Objekte in eine Liste eingefügt.
    Kopere den Code einfach mal in eine Solution und gucke im Einzelschrittdebugging was passiert.

    Die MessageBox zeigt dann einfach an, wie die Formel im Ursprungszustand ausgesehen hat (aber mit allen impliziten Operatoren (z.B. 2b -> 2*b)).
    Du müsstest dann so lange eine Schleife durchlaufen lassen, die alles soweit "normalisiert" wie möglich, bis alle Container nur noch 3 SubKeyWords haben: Operand, Operator und noch ein Operand. Dadurch kann, wenn es fixe Zahlen sind vereinfacht werden und aus dem ganzen Container wird eine fixe Zahl.
    Das Problem, bei dem ich selbst nicht durchblicke: Was bedeutet "Vereinfachen" für den Computer? Woher weiß er, dass a(b+c) einfacher ist als ab+ac? Oder wäre es anders doch besser?
    Das musst Du dann in die Logik der zweiten Schleife packen.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Sub Test() Handles Button1.Click
    2. Parse(TextBox1.Text)
    3. End Sub
    4. Sub Parse(ByVal Input As String)
    5. Dim NewFormula As New Formula
    6. For Each i As Char In Input
    7. Select Case True
    8. Case i = " "c
    9. 'Leerzeichen werden ignoriert
    10. Case Byte.TryParse(i, New Byte) 'Es handelt sich um eine Zahl
    11. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_FixedNumber Then
    12. 'Die letzte Zahl wird erweitert
    13. DirectCast(NewFormula.ContainerStack.First.SubKeyWords.Last, Formula.KeyWord_FixedNumber).Extend(i)
    14. Else
    15. 'Eine neue Zahl wird angelegt
    16. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Variable Then
    17. 'Wenn das letzte Element eine Variable ist wird ein Multiplikations-Operator hinzugefügt
    18. NewFormula.ContainerStack.First.SubKeyWords.Add(New Formula.KeyWord_Operator(Formula.KeyWord_Operator.OperatorType.Multiply))
    19. End If
    20. NewFormula.ContainerStack.First.SubKeyWords.Add(New Formula.KeyWord_FixedNumber(i))
    21. End If
    22. Case Formula.KeyWord_Operator.IsOperator(i)
    23. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Operator Then
    24. 'Zwei Operatoren aufeinander sind ungültig!
    25. Throw New Exception
    26. End If
    27. 'Ein neuer Operator wird angelegt
    28. NewFormula.ContainerStack.First.SubKeyWords.Add(Formula.KeyWord_Operator.FromChar(i))
    29. Case i = "("c
    30. 'Eine öffnende Klammer veranlasst, dass ein neuer Container in dem Container angelegt wird
    31. 'Dadurch werden zuerst Klammern berechnet
    32. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso (TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_FixedNumber OrElse TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Variable) Then
    33. 'Wenn eine Klammer auf eine Zahl oder Variable folgt wird ein Multiplikationsoperator hinzugefügt
    34. NewFormula.ContainerStack.First.SubKeyWords.Add(New Formula.KeyWord_Operator(Formula.KeyWord_Operator.OperatorType.Multiply))
    35. End If
    36. NewFormula.LevelDown()
    37. Case i = ")"c
    38. 'Eine schließende Klammer beendet den Container und es wird mit dem übergeordneten weitergearbeitet
    39. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Operator Then
    40. 'Eine schließende Klammer auf einen Operator ist ungültig
    41. Throw New Exception
    42. End If
    43. If NewFormula.LevelUp() Then
    44. Throw New Exception 'Es gibt nichts mehr zurückzuspringen (hier könnte eine schließende Klammer zu viel sein)
    45. End If
    46. Case Else
    47. 'Es handelt sich um eine Variable
    48. If NewFormula.ContainerStack.First.SubKeyWords.Count > 0 AndAlso (TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_FixedNumber OrElse TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Variable OrElse TypeOf NewFormula.ContainerStack.First.SubKeyWords.Last Is Formula.KeyWord_Contaier) Then
    49. 'Wenn eine Variable auf eine Zahl, eine andere Variable oder auf eine schließende Klammer folgt wird ein Multiplikations-Operator hinzugefügt
    50. NewFormula.ContainerStack.First.SubKeyWords.Add(New Formula.KeyWord_Operator(Formula.KeyWord_Operator.OperatorType.Multiply))
    51. End If
    52. NewFormula.ContainerStack.First.SubKeyWords.Add(New Formula.KeyWord_Variable(i))
    53. End Select
    54. Next
    55. MessageBox.Show(NewFormula.ToString)
    56. End Sub
    57. Class Formula
    58. Public Property MainKeyWordContainer As KeyWord_Contaier
    59. Public Property ContainerStack As New Stack(Of KeyWord_Contaier)
    60. Public Sub New()
    61. MainKeyWordContainer = New KeyWord_Contaier
    62. ContainerStack.Push(MainKeyWordContainer)
    63. End Sub
    64. Public Sub LevelDown()
    65. 'Es wird eine Ebene tiefer gegangen
    66. Dim NewContainer As New KeyWord_Contaier
    67. ContainerStack.First.SubKeyWords.Add(NewContainer)
    68. ContainerStack.Push(NewContainer)
    69. End Sub
    70. Public Function LevelUp() As Boolean
    71. If ContainerStack.Count > 1 Then 'Es darf nur zurückgesprungen werden, wenn dadurch nicht der MainContainer vom Stack geholt wird
    72. ContainerStack.Pop()
    73. Return False 'Es wird zurückgegeben, ob der Vorgang in nicht in Ordnung war
    74. Else
    75. Return True
    76. End If
    77. End Function
    78. 'Zur einfacheren Darstellung
    79. Public Overrides Function ToString() As String
    80. Return String.Join("", MainKeyWordContainer.SubKeyWords)
    81. End Function
    82. MustInherit Class KeyWordBaseClass
    83. End Class
    84. Class KeyWord_Variable
    85. Inherits KeyWordBaseClass
    86. Public Property VariableName As Char
    87. Public Sub New(ByVal Source As Char)
    88. VariableName = Source
    89. End Sub
    90. Public Overrides Function ToString() As String
    91. Return VariableName
    92. End Function
    93. End Class
    94. Class KeyWord_FixedNumber
    95. Inherits KeyWordBaseClass
    96. Public Property Number As Integer
    97. Public Sub New(ByVal Source As Char)
    98. 'Chars können nicht einfach so in Integer konvertiert werden. Strings schon.
    99. Number = CInt(Source.ToString)
    100. End Sub
    101. Public Sub Extend(ByVal NewChar As Char)
    102. 'z.B. "123" wird zu "1234"
    103. Number = Convert.ToInt32(Number.ToString & NewChar)
    104. End Sub
    105. Public Overrides Function ToString() As String
    106. Return Number.ToString
    107. End Function
    108. End Class
    109. Class KeyWord_Operator
    110. Inherits KeyWordBaseClass
    111. Public Enum OperatorType As Integer
    112. Add = 1
    113. Subtract = 2
    114. Multiply = 3
    115. 'Und so weiter
    116. End Enum
    117. Shared OperatorChars As New Dictionary(Of Char, OperatorType)
    118. Shared Sub New()
    119. 'Hier wird das Dictionary initialisiert (diese Sub New ist Shared!)
    120. OperatorChars.Add("+"c, OperatorType.Add)
    121. OperatorChars.Add("-"c, OperatorType.Subtract)
    122. OperatorChars.Add("*"c, OperatorType.Multiply)
    123. 'Und so weiter
    124. End Sub
    125. Public Property Type As OperatorType
    126. Public Sub New(ByVal NewType As OperatorType)
    127. Type = NewType
    128. End Sub
    129. Public Overrides Function ToString() As String
    130. Return OperatorChars.Where(Function(Duh) Duh.Value = Type).First.Key.ToString
    131. End Function
    132. Public Shared Function IsOperator(ByVal TestChar As Char) As Boolean
    133. Return OperatorChars.ContainsKey(TestChar)
    134. End Function
    135. Public Shared Function FromChar(ByVal Source As Char) As KeyWord_Operator
    136. If OperatorChars.ContainsKey(Source) Then
    137. Return New KeyWord_Operator(OperatorChars(Source))
    138. Else
    139. Throw New Exception 'Nicht zuordenbar (was eigentlich nie passieren kann, weil vorher überprüft wird, ob es überhaupt ein Operator ist)
    140. End If
    141. End Function
    142. End Class
    143. Class KeyWord_Contaier
    144. Inherits KeyWordBaseClass
    145. Public Property SubKeyWords As New List(Of KeyWordBaseClass)
    146. Public Overrides Function ToString() As String
    147. Return "(" & String.Join("", SubKeyWords) & ")"
    148. End Function
    149. End Class
    150. End Class
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Iwie brauchst Du eine iterative Funktion.
    Du musst Dich von einem Item bis zum kommunizierenden Item durchhangeln und dann diesen Ausdruck wieder untersuchen, bis Du auf das "Atom" gestoßen bist.
    Also:
    *, / bis *, /
    +, - bis +, -, *, /
    ( bis )
    usw.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Wow was für eine Informationsflut! Danke erstmal für so viele Ansätze/Vorschläge und Beispiele! Alles was man sich nur wünschen kann.
    Werde mich mal von oben nach unten durch arbeiten und sehen was bei rum kommt.

    Nur ein paar Fragen hätte ich da noch:

    @hal2000: Hast Du zum erstellen und verwenden einer Grammatik weiterführende Infos?
    Konnte bis jetzt mit Google nichts erklärendes (sondern nur fragende) finden.
    Klingt total interessant eine "Grammatik erstellen".

    @RodFromGermany: "iterative Funktion" Schon wieder etwas neues wenn ich mich nicht
    irre, laut Wikipedia geht es bei solch einer Funktion um das abarbeiten mithilfe von Schleifen.
    Meinst Du damit ich sollte eine Funktion schreiben welche im ersten Durchgang z. B. nach
    den Operatoren sucht, im Nächten Durchgang nach den Variablen etc.? (Denke so ist es eher nicht gemeint, oder?)
    Genau.
    1. Input: a + (b + c) * (d + e)
    2. Input: (b + c)
    3. Input: (d + e)
    usw.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Was soll dein Parser machen?
    Willst du Terme der Form zb ax^2 + bx + c wo a,b,c schon gegeben sind oder einen Parser, der wirklich oben genannten Term einliest? Letzteres ist nämlich ungleich schwieriger. Für ersteres könnte ich dir behilflich sein, habe auch hier im Showroom eine Library gepostet: [Release] Quadsoft.ExpressionParser - Ein Parser für mathematische Ausdrücke , die genau das kann (ersteres, also Terme wie 2,4x^2+4x+5 für gegebene x berechnen.)
    Nur als kleiner Einwurf: Vor einiger Zeit wollte ich mich hier reinlesen, hab's aber aus Zeitgründen bis jetzt rausgezögert.
    Expression Class
    Expressions-Sample

    VB.NET-Quellcode

    1. Imports System.Linq.Expressions
    2. Module Module1
    3. Sub Main()
    4. Dim multiplyXP As Expression = Expression.Multiply(
    5. Expression.Constant(5), Expression.Constant(9))
    6. Dim AddXP As Expression = Expression.Add(
    7. multiplyXP,
    8. Expression.Constant(4))
    9. Console.WriteLine(AddXP.ToString())
    10. Console.WriteLine(
    11. Expression.Lambda(Of Func(Of Integer))(AddXP).Compile()())
    12. Console.ReadKey()
    13. End Sub
    14. End Module

    Ist garantiert nicht die fertige Lösung, aber vielleicht ein weiterer Schritt in die Richtung.
    @Eistee:
    Mir ist soweit keine Seite bekannt (ich hab auch nicht direkt gesucht), die dir beim Erstellen einer Grammatik hilft. Das alles ist reine Theorie, die nur auf dem Papier / im Kopf entsteht. Ein guter Einstieg ist eventuell der Wikipedia-Artikel "Formale Grammatik". Dieser Thread könnte auch helfen: mikrocontroller.net/topic/150590. Am weitesten kommst du mit Uni-Vorlesungsskripten zum Thema Compilerbau. Alle Programmiersprachen und deren Compiler basieren auf einer formalen Grammatik, die vom Parser benutzt wird, um z.B. Syntaxfehler zu finden. Genauso kannst du dir eine Grammatik für mathematische Ausdrücke formulieren - einfach ein bisschen basteln. Verstehe aber bitte erst das Konzept, wie die sogenannten Terminale und Nichtterminale zusammenspielen, sonst geht das in die Hose.
    Gruß
    hal2000
    Und ich dachte der RegEx Berg wäre viel Arbeit :D
    Aber wenn man es macht, sollte man es schon min. halbwegs richtig machen.
    (Habwegs weil ich es nicht zu 100% so umsetzen kann wie es wohl richtig wäre).
    Sobald es Fortschritt gibt meld ich mich mal.
    Hihi - ich weiß schon, warum ich um selbstgebastelte Formel-Parserei immer schön ein Bogen mach. ;)
    Das ist mindestens ein Semester Vorlesung, das liest man sich nicht ma eben so an. Und hast du die Theorie formaler Sprachen begriffen, dann ist noch nix implementiert, da gibts dann iwas mit Tokenizer, Parser, dann wern womöglich ExpressionTrees gebildet, die dann letztendlich ein Ergebnis ausgeben können.
    Als (mich abschreckendes) Beispiel mag dienen: Calculate von Konrad Rudolph.
    Wie gesagt: ich bin lieber faul und mach wie in post#8 erwähnt.