Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check characters alternatively and replace it with Y if it is X?

Tags:

string

regex

php

I have a string, something like this:

$str ="it is a test string.";

 // for more clarification

 i t   i s   a   t e  s  t     s  t  r  i  n  g  .
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

Now I need to check all characters that are multiples of 4 (plus first character). like these:

1  => i
4  => i
8  => [space]
12 => t
16 => r
20 => .

Now, I need to compare them with Y (Y is a variable (symbol), for example Y = 'r' in here). So I want to replace Y with X (X is a variable (symbol) too, for example X = 'm' in here).

So, I want this output:

it is a test stming.

Here is my solution: I can do that using some PHP function:

  • strlen($str): to count the number of characters (named $sum)
  • $sum / 4: To find characters that are multiples of 4
  • substr($str, 4,1): to select specific character (named $char) {the problem is here}
  • if ($char == 'r') {}: to compare
  • str_replace('r','m',$char): to replace

And then combining all $char to each other.


But my solution has two problem:

  1. substr() does not count [space] character (As I mentioned above)
  2. combining characters is complicated a bit. (It needs to some waste processing)

Well, is there any solution? I like to do that using REGEX, Is it possible?

like image 660
Shafizadeh Avatar asked Nov 16 '15 07:11

Shafizadeh


3 Answers

This is an alternative using preg_replace()

$y = 'r';
$y = preg_quote($y, '/');
$x = 'M';
$x = preg_quote($x, '/');
$subject = 'rrrrrr rrrrr rrrrrr rrrr rrrr.';

$regex = "/\\G(?:^|(?(?<!^.).)..(?:.{4})*?)\\K$y/s";

$result = preg_replace($regex, $x, $subject);

echo $result;
// => MrrMrr MrrrM rrMrrr rrrM rrMr.

ideone demo


Regex:

\G(?:^|(?(?<!^.).)..(?:.{4})*?)\Km
  • \G is an assertion to the end of last match (or start of string)
  • (?:^|(?(?<!^.).)..(?:.{4})*?) matches:
    • ^ start of string, to check at position 1
    • (?(?<!^.).) is an if clause that yields:
      1. ..(?:.{4})*?) 2 chars + a multiple of 4 if it has just replaced at position 1
      2. ...(?:.{4})*?) 3 chars + a multiple of 4 for successive matches
  • \K resets the text matched to avoid using backreferences

I must say though, regex is an overkill for this task. This code is counterintuitive and a typical regex that proves difficult to understand/debug/maintain.


EDIT. There was a later discussion about performance vs. code readability, so I did a benchmark to compare:

  1. RegEx with a callback (@bobblebubble's answer).
  2. RegEx with 2 replacements in an array (@bobblebubble's suggestion in comment).
  3. No RegEx with substr_replace (@Passerby's answer).
  4. Pure RegEx (this answer).

Result:

Code #1(with_callback):   0.548 secs/50k loops
Code #2(regex_array):     0.158 secs/50k loops
Code #3(no_regex):        0.120 secs/50k loops
Code #4(pure_regex):      0.118 secs/50k loops

Benchmark in ideone.com

like image 192
Mariano Avatar answered Oct 08 '22 02:10

Mariano


Late to the party, puting aside \G anchor, I'd go with (*SKIP)(*F) method:

$str = "it is a test string.";
echo preg_replace(['~\Ar~', '~.{3}\K(?>r|.(*SKIP)(?!))~'], 'm', $str);

Short and clean.

PHP live demo

like image 28
revo Avatar answered Oct 08 '22 01:10

revo


Could just use a simple regex with callback (add u modifier if utf-8, s for . to match newline).

$str = preg_replace_callback(['/^./', '/.{3}\K./'], function ($m) {
         return $m[0] == "r" ? "m" : $m[0];
       }, $str); echo $str;

it is a test stming.

  • 1st pattern: ^. any first character
  • 2nd pattern: \K resets after .{3} any three characters, only want to check the fourth .

See demo at eval.in


For use with anonymous function PHP >= 5.3 is required. Here's the workaround.

function cb($m) { return $m[0] == "r" ? "m" : $m[0]; }
$str = preg_replace_callback(['/^./', '/.{3}\K./'], 'cb', $str);

Another demo at eval.in

like image 45
bobble bubble Avatar answered Oct 08 '22 02:10

bobble bubble