Regex két string között minden X karakter cseréje Y-ra preg_replace-szel

Egy adott mintában szeretném az összes @ karaktert : karakterre cserélni, de kizárólag azokon a részeken, amit %% és !% stringek határolnak. Szemléltetem:

           __      _   _    __           __       _    _    __             
zj@fcavaszt%%rfcazr@tcc@cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw
zj@fcavaszt%%rfcazr:tcc:cccc!%ccbfw@fcujw%%efcubzj:agew:ujca!%gewfcc@ccccaw
           ^^      ^   ^    ^^           ^^       ^    ^    ^^             

Végigtúrtam a netet, próbálkoztam saját kútfőből is, de nem jöttem rá a megoldásra. Valaki tudja esetleg?

Hozzászólások

Szerkesztve: 2020. 10. 25., v – 00:01

Preg_replace_callbackkel egyszerű, tisztán Regexppel szerintem ez nem fog menni (éjfél van, régen volt a formális nyelvek, de gyanús, hogy ehhez más push-down automaton kéne)

echo preg_replace_callback('/(?<=%%)(.*?)(?=!%)/', function($x) {
		return str_replace('@',':', $x[1]);
}, 'zj@fcavaszt%%rfcazr@tcc@cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw');

BlackY

"Gyakran hasznos ugyanis, ha számlálni tudjuk, hányszor futott le már egy végtelenciklus." (haroldking)

Működik az, csak akkor az ez történik:

 __                           __
z%%j@fcavaszt%%rfcazr@tcc@cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw
    ^        __      ^   ^    __
z%%j:fcavaszt%%rfcazr:tcc:cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw <- első %% és első !% között @ => :
                                           __                 __
z%%j:fcavaszt%%rfcazr:tcc:cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw <- második %% és első !% között @ => :, azaz semmi, mert az első szakaszon már cserélte
                                                    ^    ^
z%%j:fcavaszt%%rfcazr:tcc:cccc!%ccbfw@fcujw%%efcubzj:agew:ujca!%gewfcc@ccccaw <- harmadik %% és második !% között @ => :

A feladat csak az volt, hogy mindet cserélje, ami %% és a következő !% között van. Lehet úgy is nézni, ahogy te nézed, de ez a bejövő adatnak magának a nesting hibája, amit nem az algoritmusból kell megkerülni, mert attól csak feleslegesen sokkal lassabb lesz; a bejövő adat maga hibás: nem egyértelműsíti, hogy hol akarja nyitni a cserélendő szakaszt; mi van, ha valójában pont a második %% van rossz helyen és nem az első? Ennek megfelelően a kimenet igazából bármelyik értelmezésben helyes, de az első esetben sokkal gyorsabb.

Ha JavaScriptben kellene írnom akkor callback-kel csinálnám:

s.replace(/%%.+?!%/g, match => match.replace(/@/g, ':'));

PHP-ben ez a preg_replace_callback.

Gyanítom rekurzióval meg lehet csinálni egy kifejezéssel is, kérdés, hogy megéri-e a komplexitást a viszonylag olvasható kódból a nehezen olvasható regex kifejezésbe átvinni.

Köszi, belerakva SzBlackY kódjába

echo preg_replace_callback('/%%.+?!%/s', function($x) {
		return str_replace('@',':', $x[0]);
}, 'zj@fcavaszt%%rfcazr@tcc@cccc!%ccbfw@fcujw%%efcubzj@agew@ujca!%gewfcc@ccccaw');

ez is működik és a benchmark alapján gyorsabb is valamivel: 19235 ms vs. 15816 ms / 10000000 iteráció.

> Gyanítom rekurzióval meg lehet csinálni egy kifejezéssel is, kérdés, hogy megéri-e a komplexitást a viszonylag olvasható kódból a nehezen olvasható regex kifejezésbe átvinni.

Pont ez lenne a lényeg, hogy meg lehet-e egy lépésből regex-szel csinálni és ha igen, akkor gyorsabb-e.

A teljesség meg a szépség kedvéért Perlül:

perl -lpe 's/%%.*?!%/$&=~y|@|:|r/ge'

Nagyjából úgy, ahogy fent írták, str_replace-szel. A perlben az y avagy tr operátor való a buta, karakterenkénti cserére. De a trükk itt is az, hogy a /e módosító miatt a csere-részt nem stringként, hanem perl kifejezésként értékeli ki, azaz kódot hajt végre.

A PHP optimalizálásához nem értek, de arra tippelek, hogy ha a gyorsaság a fő célod (mert nagyon sokszor, nagyon gyakran kell ezt a cserét végrehajtanod), akkor jobb, ha egyáltalán nem használsz reguláris kifejezést, hanem írsz egy ciklust, amiben egy státuszváltozóval számon tartod, hogy épp a határoló karaktersorok között vagy-e, és aszerint cserélsz vagy nem.