Page 1 sur 2

Règle des signes

Posté : mar. 19 mai 2020 12:21
par mdanielm
Bonjour,
Je recherche une regex, une seule, qui en une passe seulement remplace des séries de signes +, -, par un seul signe, en respectant la règle des signes:

+- ou -+ donne -
-- ou ++ donne +

Bref, une meilleure solution que la solution naïve suivante:
$in = "++-+3---5++(2*-+3)+-1"
Signe($in)
ConsoleWrite($in & @crlf)

func Signe(byref $in)
   local $n
   do
      $in = StringRegExpReplace($in,"\+-|-\+", "-")
      $n=@extended
      $in = StringRegExpReplace($in,"\+\+|--", "+")
      $n+=@extended
      ;ConsoleWrite($n & @crlf)
   until $n=0
EndFunc

Re: Règle des signes

Posté : mar. 19 mai 2020 13:55
par TommyDDR
Comme dans cette règle, le "+" n'a aucune incidence, et seul un nombre pair de "-" donnent un "+", vous pouvez faire de la sorte :
Global $in = "++-+---+++-"
Signe($in)
ConsoleWrite($in & @crlf)

func Signe(byref $in)
   Local $temp = StringReplace($in, "+", "") ; On supprime les + car inutiles pour définir si on a un + ou un -
   Local $countMinus = StringLen($in) ; On compte les - restants
   $in = Mod($countMinus, 2) == 0 ? "+" : "-" ;Si on en a un nombre pair, on met un +, sinon, on met un -
EndFunc
Ce code fonctionne bien entendu que si vous fournissez une chaine avec seulement des + et des -, pas d'autre caractères, je vous laisse adapter le code si jamais vous avez besoin d'envoyer autre chose en plus.

Re: Règle des signes

Posté : mar. 19 mai 2020 14:08
par mikell
func Signe(byref $in)
   StringReplace($in, "-", "yo!")
   $in = Mod(@extended, 2) == 0 ? "+" : "-" ;Si on en a un nombre pair, on met un +, sinon, on met un -
EndFunc
:mrgreen:

Re: Règle des signes

Posté : mar. 19 mai 2020 14:18
par mdanielm
J'ai édité le message d'origine car dans ma chaîne il y a d'autres caractères!

Re: Règle des signes

Posté : mar. 19 mai 2020 17:32
par TommyDDR
Bien joué Mikell ! J'oublie tout le temps que StringReplace range le nombre de remplacement dans @extended :p

Voilà la version prenant en compte les chiffres (grossomodo, on découpe la chaine en plus de +/- consécutif et on appelle la fonction précédente pour chaque bloc).
Global $in = "++-+3---5++(2*-+3)+-1"
Signe($in)
ConsoleWrite($in & @crlf)

Func Signe(byref $in)
   Local $mode = (StringLeft($in, 1) == "+" Or StringLeft($in, 1) == "-")
   Local $retour = ""
   Local $concatPlusMoins = ""
   For $i = 1 To StringLen($in)
      Local $char = StringMid($in, $i, 1)
      Local $newMode = ($char == "+" Or $char == "-")
      If($newMode <> $mode) Then
         If($mode) Then
            $retour &= ReductionPlusMoins($concatPlusMoins)
            $concatPlusMoins = ""
         EndIf
         $mode = $newMode
      EndIf
      If($mode) Then
         $concatPlusMoins &= $char
      Else
         $retour &= $char
      EndIf
   Next
   $in = $retour
EndFunc

Func ReductionPlusMoins($in)
   Local $temp = StringReplace($in, "-", "")
   Return Mod(@extended, 2) == 0 ? "+" : "-"
EndFunc

Re: Règle des signes

Posté : mar. 19 mai 2020 18:28
par mdanielm
Ta solution est bonne mais n'est pas plus simple que la mienne et surtout beaucoup plus lente.
Je rêvais d'une regex et d'une seule passe dans le texte.

Re: Règle des signes

Posté : mar. 19 mai 2020 18:32
par TommyDDR
Attendez la réponse de mikell, si c'est faisable en regex, il vous pondra la solution telle une poule pond naturellement son oeuf :P

Re: Règle des signes

Posté : mar. 19 mai 2020 21:06
par mdanielm
Une autre solution:
func Signe3(byref $in)
   $in = StringRegExpReplace($in, "([\+-]+)", "|$1|")
   Local $arr=Stringsplit($in,'|')
   $in=""
   for $i=1 to $arr[0]
      if StringRegExp($arr[$i], "^\+|-", 0) then
         $arr[$i] = StringReplace($arr[$i], '-', '')
         $in &= mod(@extended,2)=0 ? '+' : '-'
      Else
         $in &= $arr[$i]
      EndIf
   Next
EndFunc

Re: Règle des signes

Posté : mar. 19 mai 2020 22:11
par jchd
En plus concis :

Code : Tout sélectionner

Local $in = "++-+3---5++(2*-+3)+-1-++-+---+-++-+-+4"
Signe($in)
ConsoleWrite($in & @LF)

Func Signe(ByRef $in)
	$in = Execute("'" & StringRegExpReplace($in, "([+-]+)", "' & (Mod(StringLen(StringReplace('$1', '+', '')), 2) ? '-' : '+') & '") & "'")
EndFunc
Pour la ponte, j'imagine Mikell poursuivi par un coq en rut :lol:

Re: Règle des signes

Posté : mer. 20 mai 2020 01:17
par mdanielm
Je ne connaissais pas la méthode pour appliquer une fonction à $1.
On peut l'utiliser par exemple pour vérifier la présence d'une majuscule au début des phrases, et corriger éventuellement. A moins qu'il y ait plus simple?

Re: Règle des signes

Posté : mer. 20 mai 2020 02:31
par Tlem
Bonsoir.
Moi j'aurais proposé ceci :

Code : Tout sélectionner

Func Signe4(ByRef $in)
	$in = StringReplace($in, "++", "+")
	$in = StringReplace($in, "--", "+")
	$in = StringReplace($in, "--", "+")
	$in = StringReplace($in, "+-", "-")
	$in = StringReplace($in, "-+", "-")
EndFunc
Qui est plus rapide et plus compréhensible qu'une expression régulière, malheureusement ce code ne fonctionne pas avec la chaine proposée par jchd. :cry:
Par contre ça fonctionne très bien avec la chaine proposée par mdanielm. :mrgreen: :P

Re: Règle des signes

Posté : mer. 20 mai 2020 05:08
par jchd
C'est parce qu'un simple StringReplace, comme une expression régulière, ne marche "qu'en avant", c'est-à-dire qu'il ne revient pas sur ce qu'il vient de remplacer. La récursion dans PCRE ne concerne que des éléments de pattern, pas l'emplacement en cours dans le sujet.

Si on fait ça :

Code : Tout sélectionner

StringReplace("ZaaabaabaaabbW", "aab", "")
il se passe, en séquence (souligné = déjà été traité, gras = en cours de traitement) :
ZaaabaabaaabbW
ZaaabaaabbW
ZaaabaaabbW
ZaaaabbW
ZaaaabbW
ZaabW
ZaabW
Donc si l'entrée contient un nombre non borné de sous-chaînes à remplacer qui produiront d'autres sous-chaînes sujettes à remplacement, comme ci-dessus, il faudrait autant (donc un nombre non borné !) de StringReplace pour être certain d'effectuer tous les remplacements dans le cas général. Du coup c'est soit la récursion sur l'ensemble de la chaîne en cours de traitement, soit l'astuce à la Mikell qui fonctionne dans notre cas très particulier.

Que ce soit avec récursion ou dé-récursion (suite de remplacements explicites, en nombre forcément limité) il y a une limite au-delà de laquelle ça ne fonctionne plus (explosion de la pile ou dépassement du nombre maximum préu de remplacements) et ça a été la source d'innombrables bugs et "exploits" malveillants dans d'innombrables logiciels.

Seule une boucle non bornée (do .. until ou while 1 .. wend) apporte l'assurance de résultat correct dans tous les cas, ... sauf si l'entrée est un flux lui-même non borné, auquel cas on ouvre la voie à une paralysie partielle (DoS = denial of service) si on ne met pas un stéthoscope pour arrêter les frais.

Re: Règle des signes

Posté : mer. 20 mai 2020 09:30
par Tlem
Merci JC.
En fait, avant d'écrire mon message, j'en était arrivé à ceci :

Code : Tout sélectionner

Func Signe4(ByRef $in)
	Local $StrLen
	While 1
		$in = StringReplace($in, "++", "+")
		$in = StringReplace($in, "--", "+")
		$in = StringReplace($in, "--", "+")
		$in = StringReplace($in, "+-", "-")
		$in = StringReplace($in, "-+", "-")	
		If $StrLen = StringLen($in) Then ExitLoop
		$StrLen = StringLen($in)
	WEnd
EndFunc   ;==>Signe4
Mais non seulement ce n'est pas un code plus court, mais en plus ce code met deux fois plus de temps par rapport à la fonction Signe1.

Après, avec la fonction Signe1 (qui est la plus rapide de toutes) et pour grappiller quelques milliseconde :oops: , on peux aussi faire ça :

Code : Tout sélectionner

Func Signe5(ByRef $in)
	Local $StrLen
	While 1
		$in = StringRegExpReplace($in, "\+-|-\+", "-")
		$in = StringRegExpReplace($in, "\+\+|--", "+")
		If $StrLen = StringLen($in) Then ExitLoop
		$StrLen = StringLen($in)
	WEnd
	Return $in
EndFunc   ;==>Signe5
Sur une boucle de 100000 traitements, on arrive à gagner 2 à 4 secondes, mais bon ...

Re: Règle des signes

Posté : mer. 20 mai 2020 10:40
par jchd
Je me suis conformé au cahier des charges :
Je recherche une regex, une seule, qui en une passe seulement remplace des séries de signes +, -, par un seul signe, en respectant la règle des signes

Re: Règle des signes

Posté : mer. 20 mai 2020 10:53
par jchd
Je ne connaissais pas la méthode pour appliquer une fonction à $1.
On peut l'utiliser par exemple pour vérifier la présence d'une majuscule au début des phrases, et corriger éventuellement. A moins qu'il y ait plus simple?
C'est un brin délicat à claviotter au début, à cause de l'imbrication des quotes. Mais on peut l'employer pour faire de l'interception (enfin presque) par du code natif, un peu comme Perl le permet. Il y a des limitations car c'est seulement à la fin de la partie regexp, quand tout est joué, qu'on peut appliquer le code voulu au(x) groupe(s), capturés ou pas, dans la partie "replace". Alors que Perl permet d'appliquer du code natif à tout moment pendant la regexp.

J'aimerais beaucoup avoir le temps de déployer une UDF complète pour wrapper les APIs de PCRE2, mais c'est un bien trop gros morceau pour le peu de temps libre dont je dispose.

Re: Règle des signes

Posté : mer. 20 mai 2020 21:35
par mikell
jchd a écrit : mar. 19 mai 2020 22:11Pour la ponte, j'imagine Mikell poursuivi par un coq en rut :lol:
C'est absolument hors de question

ohh.jpg
ohh.jpg (5.72 Kio) Vu 10714 fois

Sinon, juste une petite UDF - même sommaire - pour faciliter l'emploi du SRER dans un Execute, ça serait super Image
J'ai toujours eu du mal avec ces fichues quotes

Re: Règle des signes

Posté : mer. 20 mai 2020 22:21
par jchd
Quelque chose comme ça ?

Code : Tout sélectionner

Local $s = "++-+3---5++(2*-+3)+-1-++-+---+-++-+-+4"
Local $in = $s
Signe($in)
ConsoleWrite($in & @LF)

Local $sToDo = "(Mod(StringLen(StringReplace('$1', '+', '')), 2) ? '-' : '+')"
_RegExpReplacePostProc($s, "([+-]+)", $sToDo)
MsgBox(0, "", $s)

Func Signe(ByRef $s)
	$s = Execute("'" & StringRegExpReplace($s, "([+-]+)", "' & (Mod(StringLen(StringReplace('$1', '+', '')), 2) ? '-' : '+') & '") & "'")
EndFunc

Func _RegExpReplacePostProc(ByRef $sSubject, $sPattern, $sCode)
	$sSubject = Execute("'" & StringRegExpReplace($sSubject, $sPattern, "' & " & $sCode & " & '") & "'")
EndFunc
Bon, c'est brut de cul de poule (mpff !) mais ça marche.

On peut aussi prévoir de passer une array de codes différents (toujours en string) à appliquer à $1, $2, $3, ... mais le problème est alors de ficeler ensemble toutes les parties et plus il y en a plus ça devient tortueux.
Faut oublier, c'est ingérable !

Re: Règle des signes

Posté : jeu. 21 mai 2020 08:58
par mikell
Oui quelque chose comme ça...
Avec une backreference c'est déjà bien
Je vais essayer de trafiquer ta fonction pour qu'elle affiche l'instruction finale avant son exécution
Image

Re: Règle des signes

Posté : jeu. 21 mai 2020 13:14
par jchd
J'ai regardé vite fait : 90% des utilisations que j'en ai eu (pour moi ou pour répondre à des demandes) il n'y avait qu'un groupe ($1) et le reste, quand on a besoin de $1, $2, $3 ... on peut tout grouper dans une seule expression/fonction.

Donc je dirais qu'en cas de besoin plus sophistiqué, on cpature tout ce qu'il faut et on traite les morceaux avec le code ad hoc.

Re: Règle des signes

Posté : jeu. 21 mai 2020 18:47
par mikell
Très pratique en tout cas pour mon petit blocage avec les quotes...
Du coup je me suis fait la petite gui qui va bien :mrgreen: c'est pas infaillible mais ça va rudement me simplifier la vie
Purée comment j'y ai pas pensé avant ? :roll:

$gui = GuiCreate("constructeur Execute SRER", 950, 110, -1, 100)
GUISetFont(12, 400, 0, "Times New Roman", $gui, 5)
GuiCtrlCreateLabel("pattern", 20, 8, 100, 20)
GuiCtrlCreateLabel("remplacement (utiliser simples quotes)", 390, 8, 300, 20)
$input1 = GuiCtrlCreateInput("", 10, 30, 360, 25)
$input2 = GuiCtrlCreateInput("", 380, 30, 500, 25)
$btn = GuiCtrlCreateButton("go", 890, 30, 50, 25)
$input3 = GuiCtrlCreateInput("", 10, 70, 930, 25)
GuiSetState()

While 1
  $msg = GuiGetMsg()
  If $msg = -3 Then Exit
  If $msg = $btn Then
   $tomatch = GuiCtrlRead($input1)
   $todo = GuiCtrlRead($input2)
   $instr = 'Execute("''" & StringRegExpReplace($s, "' & $tomatch & '", "'' & ' & $todo & ' & ''") & "''")'
   GuiCtrlSetData($input3, $instr)
  EndIf
Wend