【问题标题】:How to check characters alternatively and replace it with Y if it is X?如果是X,如何交替检查字符并用Y替换它?
【发布时间】:2016-02-17 06:04:00
【问题描述】:

我有一个字符串,像这样:

$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

现在我需要检查所有是 4 的倍数的字符(加上第一个字符)。像这样:

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

现在,我需要将它们与Y比较Y 是一个变量(符号),例如这里的Y = 'r'。所以我想用X 替换YX 也是一个变量(符号),例如这里的X = 'm')。

所以,我想要这个输出:

it is a test stming.

这是我的解决方案:我可以使用一些 PHP 函数来做到这一点:

  • strlen($str):统计字符数(命名为$sum
  • $sum / 4: 寻找4的倍数的字符
  • substr($str, 4,1):选择特定字符(命名为$char {问题就在这里}
  • if ($char == 'r') {}:比较
  • str_replace('r','m',$char): 替换

然后将所有$char 相互组合。


但我的解决方案有两个问题

  1. substr() 不算[space] 字符(如上所述)
  2. 组合字符有点复杂。 (需要进行一些废物处理)

嗯,有什么解决办法吗?我喜欢用 REGEX 来做,有可能吗?

【问题讨论】:

  • 您能否更具体地了解“substr() 不计入[space]”?这个我看不懂。
  • $chars = explode('', $string); 会给你一个包含所有字符的数组。这应该为您指明正确的方向。
  • @Passerby echo substr($str, 4, 1)=> 输出:ss 是第五个字符。所以substr() 不算[空格]
  • @Shafizadeh 否......字符串和数组索引从 0 开始计数,而不是 1。
  • @Passerby 哦,是的,看来你是对的,谢谢。

标签: php regex string


【解决方案1】:

可以只使用simple regexcallback(如果是utf-8,则添加u modifiers 用于. 以匹配换行符)。

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

这是一个测试。

  • 第一个模式:^. 任何第一个字符
  • 第二个模式:\K.{3}任意三个字符后重置,只想检查第四个.

See demo at eval.in


使用匿名函数 PHP >= 5.3 是必需的。这是解决方法。

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

Another demo at eval.in

【讨论】:

  • 很好,现在完全正确,如果@anubhava 拒绝编辑他的答案,我会将其标记为我的答案。
  • 这是一个很好的答案+1。 @Shafizadeh:对不起,我不得不赶去开会。回来后,我注意到了很好的新答案,所以删除了我的,因为它需要更正。
  • @anubhava 啊,我明白了。只有一件事:正如您所说,这个答案很好,是的,我知道,但是您对马里亚诺的回答有何看法?它甚至没有条件,它是纯正则表达式......!那么,是哪一个?
  • 这个解决方案更容易理解和维护,但它不是纯正则表达式解决方案。如果有选择,我会在我的代码中保留可理解的解决方案。
  • @Shafizadeh 我也会选择你的代码,或者任何编辑你的代码的人,可以很容易地理解/调试/维护。回调的唯一缺点是性能。基准测试:ideone.com/5ZFCFe
【解决方案2】:

如果你的字符串中的所有字符都是单字节,你可以使用来自PHP's official language reference的东西:

$str ="it is a test string.";
$y="r";
$x="m";
$len=strlen($str);
if($str[0]==$y)
{
    $str=substr_replace($str,$x,0,1);
}
if($len>=3)
{
    for($i=3;$i<$len;$i+=4)
    {
        if($str[$i]==$y)
        {
            $str=substr_replace($str,$x,$i,1);
        }
    }
}
var_dump($str);

3v4l demo

输出it is a test stming.


编辑:

正如@Don'tPanic 指出的那样,使用[] 运算符,String 是可变的,所以不要使用

$str=substr_replace($str,$x,$i,1);

你可以使用

$str[$i]=$x;

【讨论】:

  • 顺便说一句,你也可以直接用$str[$i] = $x修改字符串,而不用substr_replace
  • @Don'tPanic 哎呀,没有意识到这一点。谢谢!我会将此更新为我的答案。
【解决方案3】:

这是使用 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


正则表达式:

\G(?:^|(?(?<!^.).)..(?:.{4})*?)\Km
  • \G 是对最后一个匹配结束(或字符串开始)的断言
  • (?:^|(?(?&lt;!^.).)..(?:.{4})*?) 匹配:
    • ^ 字符串开头,在位置 1 处检查
    • (?(?&lt;!^.).) 是一个 if 子句,它产生:
      1. ..(?:.{4})*?) 2 个字符 + 4 的倍数(如果它刚刚在位置 1 替换)
      2. ...(?:.{4})*?) 3 个字符 + 4 的倍数用于连续匹配
  • \K 重置匹配的文本以避免使用反向引用

但我必须说,正则表达式对于这项任务来说太过分了。这段代码违反直觉,是一个典型的正则表达式,难以理解/调试/维护。


编辑。后来有关于性能与代码可读性的讨论,所以我做了一个基准来比较:

  1. 带有回调的正则表达式 (@bobblebubble's answer)。
  2. RegEx 在一个数组中有 2 个替换项 (@bobblebubble's suggestion in comment)。
  3. 没有带有substr_replace (@Passerby's answer) 的正则表达式。
  4. 纯正则表达式(此答案)。

结果:

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

【讨论】:

  • 为什么要投反对票?它不能正常工作?我测试了它,我认为它也可以工作......
  • 据我所知,(?:.{4})*? 部分完全是多余的。
  • 另外,“但我必须说,正则表达式对于这项任务来说太过分了。”我不会说 overkill,但这将是一场维护噩梦:我确定@Shafizadeh 不想回到 SO 让某人解释正则表达式到底在做什么. :-)
  • @bobblebubble 确实如此。我写下了逻辑,结果一切都颠倒了。它不遵循“自然逻辑”来提高效率。例如,我以((.{4})*?.{3}) 开头,在(.{4})*? 之前以.{3} 结尾。
  • @salathe 我同意,这是一个典型的正则表达式,很难维护。但是OP明确表示他想要这种解决方案。希望它有助于鼓励人们更深入地了解正则表达式。
【解决方案4】:

试试这个

$str ="it is a test string.";
$y="r";
$x="m";    

$splite_array = str_split($str);

foreach ($splite_array as $key => $val)
{
    if($key % 4 == 0 && $val == $y)
    {
        $splite_array[$key] = $x;
    }
}

$yout_new_string  = implode($splite_array);

【讨论】:

  • 这个答案有一个很好的算法,但老实说对我不起作用,为什么它获得了 4 个赞?!谁能给我这个解决方案的演示?
  • 在一个页面上试试这个代码,然后你可以意识到它工作正常,我想你可以检测到你的错误
  • @Shafizadeh See this demo,我也投了赞成票,因为想法很简洁。
  • 您可能希望将此$splite_array[$key] = $x; 更改为此$str[$key] = $x; 并避免使用implode 函数。
【解决方案5】:

这段代码可以帮助你:

// Define variables
$string = "it is a test string.";
$y = 'r';
$x = 'm';

// Convert string to array
$chars = explode('', $string);

// Loop through all characters
foreach ($chars as $key => $char) {
    // Array keys start at 0, so we add 1
    $keyCount = $key+1;

    // Check if deviding the key by 4 doesn't have rest value
    // This means it is devisable by 4
    if ($keyCount % 4 == 0 && $value == $y) {
        $chars[$key] = $x;
    }
}

// Convert back to string
$string = implode($chars);

【讨论】:

    【解决方案6】:

    这是使用string access and modification by character 的另一种方法。 (因此,它只对单字节编码的字符串有用。)

    // First character handled outside the loop because its index doesn't match the pattern
    if ($str[0] == $y) $str[0] = $x;
    
    // access every fourth character
    for ($i=3; isset($str[$i]) ; $i+=4) {
        // change it if it needs to be changed
        if ($str[$i] == $y) $str[$i] = $x;
    }
    

    这会修改原始字符串而不是创建新字符串,因此如果不应该发生这种情况,则应在副本上使用它。

    【讨论】:

      【解决方案7】:

      派对迟到了,抛开\G主播,我会选择(*SKIP)(*F)方法:

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

      短而干净。

      PHP live demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-03-21
        • 2012-12-03
        • 2011-09-18
        • 2017-12-06
        • 1970-01-01
        相关资源
        最近更新 更多