Filterungsproblem von Klammern ( vielleicht mit Regexp?)

  • Hallo Community,
    ich habe vor, einen kleinen Parser für spezielle Funktionen zu schreiben, der jedoch Prioritäten durch Klammern unterstützen soll:S.
    Kleines Beispiel:
    ((a+b)-(a*(b+a)))+b

    Wie kann ich herausfinden, dass ein Term von den meisten Klammern umgeben ist?
    Also in dem Fall sollte zuerst (b+a) berechnet werden.
    Somit wäre (b+a) das Prioritätselement: 1
    (a+b) und a*(b+a) sind Prioritätselement: 2
    (a+b)-(a*(b+a)) ist Prioritäselement: 3
    ((a+b)-(a*(b+a)))+b ist Prioritätselement: 4

    Code
    Pseudocode:
    $Gleichungen[0][0] = Anzahl der verschiedenen Prioritätselemente; in dem Fall 4
    $Gleichungen[0][1] = maximale Anzahl gleichwertiger Prioritätslemente ; in dem Fall 2
    $Gleichungen[1][0] = Prioritätslement 1, Nummer 1; also in dem Fall (b+a)
    $Gleichungen[2][0] = Prioritätslement 2, Nummer 1; also in dem Fall (a+b)
    $Gleichungen[2][1] = Prioritätslement 2, Nummer 2; also in dem Fall a*(b+a)
    $Gleichungen[3][0] = Prioritätslement 3, Nummer 1; also in dem Fall (a+b)-(a*(b+a))
    $Gleichungen[4][0] = Prioritätslement 4, Nummer 1; also in dem Fall ((a+b)-(a*(b+a)))+b

    Leider habe ich keine Ahnung wie ich herausfinden kann, welcher Term z.B. Prioritätselement 1 ist.
    Meine Idee war zunächst mit Stringreplace und @extended die Anzahl der "(" zu finden, aber das half mir nicht sonderbar.

    Sollte ich mich nicht verständlich ausgedrückt haben, formuliere ich Passagen gerne präziser/deutlicher :whistling: .
    Ich hoffe, obwohl ich noch keinen ersten Code geschrieben habe, dass HuU das richtige Forum ist. :rolleyes:
    Vielleicht hat jemand schonmal so etwas gesehen oder eine gute Idee wie es umzusetzen ist. Jeder Ansatz hilft mir ;)

    Wer immer nur das tut, was er bereits kann - wird auch immer nur das bleiben, was er bereits ist!

    Einmal editiert, zuletzt von XovoxKingdom (23. Oktober 2010 um 18:16)

    • Offizieller Beitrag

    Mir würde da jetzt eine rekursive Funktion einfallen.
    Jede öffnende Klammer ruft die Funktion erneut auf, der Term bis zur schliessenden Klammer wird gelesen, ausgerechnet und per Return zurückgegeben.
    Nach der Rückgabe wird das ausgewertete Klammernpaar entfernt und durch den Returnwert ersetzt. So müsste sich das eigentlich lösen lassen.
    Hab jetzt aber keine Zeit, die Funktion zu schreiben. Ist also erstmal nur theoretisch...

  • Rekursiv auf jeden Fall, allerdings würde ich von der letzten="innersten" öffnenden Klammer ausgehen und dann bis zur schliessenden einlesen.
    nächster Rekursionsschritt:vorletzte öffnende bis zur 2. schliessenden Klammer usw

  • Spoiler anzeigen
    [autoit]

    $string = "((a+b)-(a*(b+a)))+b"
    $k = 1 ;klammerebene

    [/autoit] [autoit][/autoit] [autoit]

    _terme($string, $k)

    [/autoit] [autoit][/autoit] [autoit]

    Func _terme($a, $k)
    $o = StringInStr($a, "(", 1, -$k) ;öffnende Klammer
    If $o = 0 Then ;keine klammer mehr gefunden
    MsgBox(0, 0, $a)
    Return ;rekursionsebene zurück
    EndIf
    $s = StringInStr($a, ")", 1, $k, $o) + 1;schliessende Klammer
    $term = StringMid($a, $o, $s - $o) ;dazwischen
    MsgBox(262144, 'Debug line ~' & @ScriptLineNumber, 'Selection:' & @LF & '$term' & @LF & @LF & 'Return:' & @LF & $term) ;### Debug MSGBOX
    _terme($a, $k + 1) ;neu aufrufen, nächste klammerebene
    Return ;rekursionsebene zurück
    EndFunc ;==>_terme

    [/autoit]

    ciao
    Andy


    "Schlechtes Benehmen halten die Leute doch nur deswegen für eine Art Vorrecht, weil keiner ihnen aufs Maul haut." Klaus Kinski
    "Hint: Write comments after each line. So you can (better) see what your program does and what it not does. And we can see what you're thinking what your program does and we can point to the missunderstandings." A-Jay

    Wie man Fragen richtig stellt... Tutorial: Wie man Script-Fehler findet und beseitigt...X-Y-Problem

    Einmal editiert, zuletzt von Andy (23. Oktober 2010 um 18:09)

  • Ok, Andy hat jetzt schon ne Lösung, aber wenn man alles während dem Programmablauf ausrechnen kann könnte man es vielleicht auch so machen:

    Spoiler anzeigen
    [autoit]

    Local $formel = '((a+b)-(a*(b+a)))+b'

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    Local $posauf = 0
    Local $poszu = 0

    [/autoit] [autoit][/autoit] [autoit]

    For $i = 1 To StringLen($formel)
    If StringMid($formel, $i, 1) = '(' Then
    $posauf = $i
    ElseIf StringMid($formel, $i, 1) = ')' Then
    $poszu = $i
    ExitLoop
    EndIf
    Next

    [/autoit] [autoit][/autoit] [autoit]

    if ($posauf = 0) and ($poszu = 0) Then ExitLoop

    [/autoit] [autoit][/autoit] [autoit]

    $buffer = StringMid($formel, $posauf, $poszu - $posauf+1)
    $formel = StringReplace($formel, $buffer, '#')

    [/autoit] [autoit][/autoit] [autoit]

    MsgBox(0, "", $formel & @CRLF & @CRLF & 'Ersetzt wurde: ' & $buffer)
    WEnd

    [/autoit]

    Andys Lösung ist aber eleganter :)

  • @Oscar & Andy: Danke für die Idee mit der Rekursion.
    Leider ist das Neuland für mich (werde ich mir mal zu Gemüte führen).
    Ich denke das Problem ist (vorerst) gelöst. ;)
    @Andy: Vielen Dank auch für das Codebeispiel, das mir sicherlich die Funktionsweise der Rekursion näher bringt! :)

    Und wieder einmal muss ich dieses unglaublich kompetente Forum loben, das sich auch für Anfänger die Zeit nimmt,
    die Programmierung näher an den Mann zu bringen. :thumbup:

    Wer immer nur das tut, was er bereits kann - wird auch immer nur das bleiben, was er bereits ist!

  • Na ja, ist ein bisschen spät, aber ...

    ... wenn die Klammerpaare immer nur einen Operator enthalten und man sich auf die Grundrechenarten beschränkt, kann man das auch so lösen:

    [autoit]

    $a = 3
    $b = 5
    $Term = "((a+b)-(a*(b+a)))+b"
    $Term = "(" & $Term & ")"
    $I = 0
    $aResult = StringRegExp($Term, "\(([^(]+?)\)", 1)
    While Not @error
    $aSplit = StringRegExp(StringStripWS($aResult[0], 8), "\A(-?[^*/+-]+)([*/+-])(-?.+)\Z", 1)
    If IsDeclared($aSplit[0]) Then $aSplit[0] = Eval($aSplit[0])
    If IsDeclared($aSplit[2]) Then $aSplit[2] = Eval($aSplit[2])
    $Ergebnis = Execute($aSplit[0] & $aSplit[1] & $aSplit[2])
    $Term = StringRegExpReplace($Term, "\(\Q" & $aResult[0] & "\E\)", $Ergebnis, 1)
    $I += 1
    MsgBox(0, "Lösung", "Schritt " & $I & ":" & @CRLF & @CRLF & $Term)
    $aResult = StringRegExp($Term, "\(([^(]+?)\)", 1)
    WEnd
    Exit

    [/autoit]


    Die Auflösung der Klammern muss ja nur von Innen nach Außen aber nicht zwingend von Recht nach Links erfolgen.

  • Hi,
    ich habe mir mal ein paar Gedanken gemacht, und das ist dabei rausgekommen.
    Bestimmt net die beste Lösung aber mal eine.

    [autoit][/autoit]
    Spoiler anzeigen
    [autoit]

    #include <Array.au3>
    ;Dim $string = "((a+b)-(a*(b+a)))+b"
    Dim $string = "((1+7)+(2*(2+3-1)))/4"
    Dim $string_edit = "("

    $a = 0

    While 1
    if StringRegExp($string_edit, '(\()', 0) = 1 then
    if ($a = 0) then
    $string_edit = _between_bracket($string)
    ;MsgBox(0,$a,$string_edit)
    $a+=1
    else
    $string_edit = _between_bracket($string_edit)
    ;MsgBox(0,$a,$string_edit)
    $a+=1
    EndIf
    Else
    $string_edit = _calculate_string($string_edit)
    MsgBox(0,"Ende",$string_edit)
    ExitLoop
    EndIf
    Wend


    Func _between_bracket($f_string)
    $array = StringRegExp($f_string, '\(([^(]+?)\)', 3)
    ;_ArrayDisplay($array,"_between_bracket")
    $i = 0
    While $i <= UBound($array)-1
    $f_string = StringReplace($f_string, "("&$array[$i]&")",_calculate_string($array[$i]))
    $i+=1
    Wend
    return $f_string
    EndFunc


    Func _calculate_string($c_string)
    Local $value
    Local $splitvalue[1]
    Local $ergstring = 0


    if Number(StringLeft($c_string, 1)) > 0 then $c_string = "+"&$c_string
    $splitvalue = StringRegExp($c_string, '([^\d])(\d+)', 3) ;-4+10-1

    ;_ArrayDisplay($splitvalue )

    $a=1
    $splitvalue[$a] = $splitvalue[0]&$splitvalue[$a]
    Do
    if (($splitvalue[$a+1] = "+" ) AND ($ergstring = 0))then
    $ergstring = $splitvalue[$a]+$splitvalue[$a+2]
    $a+=2
    ElseIf (($splitvalue[$a+1] = "+" ) AND ($ergstring > 0)) then
    $ergstring = $ergstring+$splitvalue[$a+2]
    $a+=2
    ElseIf (($splitvalue[$a+1] = "-" ) AND ($ergstring = 0))then
    $ergstring = $splitvalue[$a]-$splitvalue[$a+2]
    $a+=2
    elseif (($splitvalue[$a+1] = "-" ) AND ($ergstring > 0)) then
    $ergstring = $ergstring-$splitvalue[$a+2]
    $a+=2
    elseif (($splitvalue[$a+1] = "*" ) AND ($ergstring = 0))then
    $ergstring = $splitvalue[$a]*$splitvalue[$a+2]
    $a+=2
    ElseIf (($splitvalue[$a+1] = "*" ) AND ($ergstring > 0)) then
    $ergstring = $ergstring*$splitvalue[$a+2]
    $a+=2
    ElseIf (($splitvalue[$a+1] = "/" ) AND ($ergstring = 0))then
    $ergstring = $splitvalue[$a]/$splitvalue[$a+2]
    $a+=2
    elseif (($splitvalue[$a+1] = "/" ) AND ($ergstring > 0)) then
    $ergstring = $ergstring/$splitvalue[$a+2]
    $a+=2
    EndIf
    Until $a = UBound($splitvalue)-1

    Return $ergstring
    EndFunc

    [/autoit]
    [autoit][/autoit]

    Einfach mal testen und melden was rumgekommen ist.

    MfG
    Der_Doc

  • Vielen Dank für die weiteren Lösungen :)
    Ich denke die schrittweise Zerlegung von Großvater ist die passend(st)e Lösung,
    die ich flexibel anpassen kann.
    @Der_Doc: Deine Lösung werde ich mit der anderen kombinieren :P

    Und nochmals Danke für die kompetente Unterstützung :thumbup:

    Wer immer nur das tut, was er bereits kann - wird auch immer nur das bleiben, was er bereits ist!