StringReplace mit RegExp-Pattern und Manipulation jedes Matches per Iteration

    • Offizieller Beitrag

    Hi,
    folgende Problemstellung als Bsp.:
    Ein Text enthält unbekannt viele Werte eines bestimmten Musters (z.B. ZAHLENWERT-SPACE-EINHEIT_KG) jetzt möchte ich alle diese Kg-Angaben in Gramm umwandeln.
    Die Matches per RegExp zu finden ist kein Problem.
    Mein Gedanke war nun, kann ich mit StringRegExpReplace während des Parsens den Match manipulieren und Replace mit dem geänderten Wert durchführen? Jein. Ich kann das führ den ersten Match durchführen, muß dann aber wieder den kpl. String weiterparsen, bis alle Vorkommen bearbeitet und ersetzt sind. Und zusätzlich muß ich das Pattern so aufbauen, dass auch immer der kpl. Text in Backreferenzen gecaptured wird damit ich den zu manipulierenden Wert auch abgreifen kann ohne dass der Rest verloren geht. Bei Texten mit einigen 1000 Zeilen ist mir diese Methode deutlich zu aufwändig (Ressourcen-verbratend).
    Nächster Gedanke: Per Offset-Parsing die Fundstellen und Matches ermitteln und zwischenspeichern. Mit den Offset-Infos die Teilstrings selektieren, den Match manipulieren und einen neuen Ergebnisstring zusammensetzen. Das funktioniert auch - allerdings bei größeren Dimensionen (Text auf 6500 Zeilen mit 3800 Matches) werden bei mir ca. 30 s benötigt (inkl. Konsolenausgabe).

    Vielleicht habt ihr eine Idee, wie man das performanter gestalten kann.
    Bei ein paar hundert Zeilen Text ist das kein Problem, somit in der überwiegenden Zahl der Anwendungsfälle ausreichend. Ich muß aber z.B. öfter Maschinen-Logs abgrasen und da wäre es durchaus schick, wenns auch schneller geht.

    Hier mal meine Lösung, in dem Bsp-String ersetze ich Zahlenwerte in eckigen Klammern mit dem Wert*1000:

    Spoiler anzeigen
    [autoit]

    $s = "abc[1] gjj66 h [20]j4l7l3 l[3]-g9 j[80lh[444][5]k21k]kq" ; alle Zahlen in eckigen Klammern sollen mit ihrem Wert*1000 ersetzt werden
    $p = "(\[\d+\])" ; Search-Pattern
    $sRet = _StrReplaceIterated($s, $p, '_GetManipulatedMatch') ; Funktionsaufruf mit Angabe der Funktion zur Manipulation der Matches
    ConsoleWrite('Anzahl Ersetzungen:' & @extended & @LF)
    ConsoleWrite($sRet & @LF)

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

    Func _StrReplaceIterated($_s, $_sPatt, $_sReplFunc)
    Local $aMatch, $iOff = 1, $sMatch = '', $sOff = '', $aPos, $sPart, $iStart = 1, $sOut = ''
    While 1
    $aMatch = StringRegExp($_s, $_sPatt, 1, $iOff)
    If Not @error Then
    $iOff = @extended
    $sMatch &= $aMatch[0] & Chr(0)
    $sOff &= $iOff & ','
    Else
    ExitLoop
    EndIf
    WEnd
    If $sOff <> '' Then
    $aPos = StringSplit(StringTrimRight($sOff, 1), ',')
    $aMatch = StringSplit(StringTrimRight($sMatch, 1), Chr(0))
    EndIf
    If Not IsArray($aPos) Then Return SetError(1,0,0)
    For $i = 1 To $aPos[0]
    $sPart = StringTrimRight( StringMid($_s, $iStart, $aPos[$i]-$iStart), StringLen($aMatch[$i]) )
    $sOut &= $sPart & Call($_sReplFunc, $aMatch[$i])
    $iStart = $aPos[$i]
    Next
    $sOut &= StringRight($_s, StringLen($_s)-$aPos[$aPos[0]])
    Return SetError(0, $aPos[0], $sOut)
    EndFunc ;==> _StrReplaceIterated

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

    ; Funktion zur Manipulation der Matches
    Func _GetManipulatedMatch($_s)
    Local $i = StringTrimRight(StringTrimLeft($_s, 1), 1) *1000
    Return "[" & $i & "]"
    EndFunc

    [/autoit]
  • Ich hätte das mit StringReplace gelöst:

    [autoit]

    Func _StrReplaceIterated($_s, $_sPatt, $_sReplFunc)
    Local $aMatch, $i, $sCall
    $aMatch = StringRegExp($_s, $_sPatt, 3)
    If @error Then Return SetError(1, @error, $_s)
    For $i = 0 To UBound($aMatch)-1
    $sCall = Call($_sReplFunc, $aMatch[$i])
    If @error Then Return SetError(2, @error, $_s)
    $_s = StringReplace($_s, $aMatch[$i], $sCall, 1, 1)
    Next
    SetExtended(UBound($aMatch))
    Return $_s
    EndFunc

    [/autoit]
  • Hi

    Hier ein weiterer Ansatz:

    [autoit]

    Func _StrReplaceIterated($_s, $_sPatt, $_sReplFunc)
    Local $sRE_Rep = StringRegExpReplace($_s, $_sPatt, Chr(1) & "$1" & Chr(1))
    Local $aSplit = StringSplit($sRE_Rep, Chr(1))
    Local $sOut = ""

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

    For $i = 1 To $aSplit[0] - 1 Step 2
    $sOut &= $aSplit[$i] & Call($_sReplFunc, $aSplit[$i + 1])
    Next
    $sOut &= $aSplit[$aSplit[0]]

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

    Return SetError(0, $aSplit[0] / 2, $sOut)
    EndFunc ;==>_StrReplaceIterated

    [/autoit]

    lgE

  • Hallo BugFix, ich stell dir hier mal einer meiner Lieblings Funktion "_StringRegExpReplace_Callback" zu Verfügung :)

    [autoit]

    ; #FUNCTION# ====================================================================================================================
    ; Name ..........: _StringRegExpReplace_Callback
    ; Description ...:
    ; Syntax ........: _StringRegExpReplace_Callback($sString, $sPattern, $sCallback[, $sBefore = ''[, $sAfter = '']])
    ; Parameters ....: $sString
    ; $sPattern
    ; $sCallback
    ; $sBefore
    ; $sAfter
    ; Return values .: None
    ; Author ........: Taz77 (Markus Ehmig <autoit at ehmig dot net>)
    ; Since .........: 2012/04/23
    ; Modified ......: 2013/05/11
    ; Remarks .......: Achtung! Das ' wird temporär im $sString durch \x1a ersetzt, wenn im plattern danach gesucht werden soll, muss nach \x1a enstelle von ' gesucht werden
    ; Related .......:
    ; Link ..........:
    ; Example .......: No
    ; ===============================================================================================================================
    Func _StringRegExpReplace_Callback($sString, $sPattern, $sCallback, $sBefore='', $sAfter='')
    Local $Str = Execute("'" & StringRegExpReplace(StringReplace($sString, "'", Chr(26), 0, 2), $sPattern, $sBefore&"'&" & $sCallback & "&'"&$sAfter) & "'")
    If @error then
    ConsoleWrite('_StringRegExpReplace_Callback error! Pattern: "'&$sPattern&'", Callback: "'&$sCallback&'"' & @LF)
    Return $sString
    EndIf
    Return StringReplace($Str, Chr(26), "'", 0, 2)
    EndFunc

    [/autoit]

    Anwendungsbeispiel für dein Problem:

    [autoit]

    $String = "abc[1] gjj66 h [20]j4l7l3 l[3]-g9 j[80lh[444][5]k21k]kq" ; alle Zahlen in eckigen Klammern sollen mit ihrem Wert*1000 ersetzt werden
    $sRet = _StringRegExpReplace_Callback($String, "\[(\d+)\]", "__CallBack_Multiplikation('\1')", '[', ']')
    ConsoleWrite($sRet & @LF)

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

    Func __CallBack_Multiplikation($sInput)
    Return Int($sInput) * 1000
    EndFunc

    [/autoit]


    Ich hoffe das hilft dir weiter :)

  • Für das letzte bisschen Performance hab ich noch eine optimierte Variante anzubieten:

    [autoit]

    Func _StrReplaceIterated($sS)
    Local $sRE = StringRegExpReplace($sS, "(\[)(\d+)(\])", "$1" & Chr(1) & "$2" & Chr(1) & "$3")
    Local $aS = StringSplit($sRE, Chr(1))
    Local $sO = ""

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

    For $i = 1 To $aS[0] - 1 Step 2
    $sO &= $aS[$i] & $aS[$i + 1] * 1000
    Next
    $sO &= $aS[$aS[0]]

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

    Return SetError(0, $aS[0] / 2, $sO)
    EndFunc ;==>_StrReplaceIterated

    [/autoit]

    Die Berechnung erfolgt innerhalb der Funktion und funktioniert deshalb nur für diesen Einsatzzweck!
    Deshalb ist auch Taz´s Version die bessere!

    Taz77: WOW - die Funktion ist wirklich sehr gut! :rock:

    E

    • Offizieller Beitrag

    Test-Text:
    Zeilen: 6452
    Zeichen gesamt: 876133
    Matches: 3752

    Text und Pattern in Variablen eingelesen, dann in einer Schleife 50-mal abwechselnd alle Funktionen aufgerufen. (von eukalyptus habe ich die allgemeingültige Variante getestet)
    Hier das Ergebnis:

    Code
    Minimum    Maximum   Durchschnitt
    BugFix           15.69      18.87      17.60
    James            16.66      19.11      17.95
    eukalyptus        0.07       0.15       0.11
    Taz77             0.06       0.12       0.09

    Taz77: Du liegst mit deiner Funktion einen Hauch vor eukalyptus. :D ( und nur 0,5% Zeitaufwand im Vergleich zu meiner Funktion :wacko: )

    Dass James Variante langsamer ist als meine hatte ich vom reinen Anschauen her nicht erwartet, da ich ja 2-mal eine Schleife über alle Matches laufen lasse und er nur einmal. Aber hier wird wohl StringReplace die Bremse sein, da dort jedes mal der gesamte String dupliziert werden muss.

    Also vielen Dank nochmal für eure Mitwirkung.
    Und Taz77 - wäre schick, wenn du solche Funktionen im Skripte-Forum publizierst. Damit brauchst du wirklich nicht hinterm Berg halten. :thumbup: