【问题标题】:PHP preg_replace occurrence of any text after the very last underscorePHP preg_replace 在最后一个下划线之后出现的任何文本
【发布时间】:2012-04-19 20:13:26
【问题描述】:

我可以使用explode 来做到这一点(或者与strrpos 一起玩),但我更喜欢使用preg_replace,因为我认为应该更快一些(不是吗?)。另外,它简洁优雅。

目的是,给定一个类似a_b_c 的字符串,以获取另一个字符串其中最后一个_ 之后的字符 替换为传递的字符串。

我不擅长正则表达式。我得找时间买一本好书学习。无论如何,我已经尝试过这个正则表达式'/_(.*)$/',因为 匹配字符串的结尾,捕获最后一个下划线之后的任何字符

我的论证有什么问题?

// Do it using explode
function foo($string, $replacement)
{
    $pieces = explode('_', $string);
    array_pop($pieces);
    return implode('_', array_merge($pieces, array($replacement)));
}

// Do it using regular expression (not working)
function bar($string, $replacement)
{
    return preg_replace('/_(.*)$/', $replacement, $string);
}

echo foo('a_b_c', 3); // Prints a_b_3
echo bar('a_b_c', 3); // Prints a3 wrong!!!

【问题讨论】:

  • 我遇到了问题。我想要正则表达式!现在我有两个问题;)
  • preg 比简单的字符串函数慢 .. 在大多数情况下
  • @Hajo 同意 - 在某些情况下,它会慢得多,具体取决于使用的实际正则表达式;看我的回答!

标签: php regex preg-replace preg-match


【解决方案1】:

你很接近,你正在寻找的模式是这样的:

([^_]*)$

这将仅匹配非下划线的文本,尽可能靠近字符串的末尾。它也不包括下划线,因为您的替换字符串不使用$1 来指定匹配组。

【讨论】:

  • 这行得通,但是当您真正想要的是一组更具限制性的匹配字符时,使用非贪婪匹配并不是最佳做法。面对格式的变化,它很脆弱,无法清楚地传达意图。我推荐 Friedl 的书来讨论这个话题。
  • 我应该将任何参数传递给 preg_replace 吗?即使添加惰性字符似乎也不起作用。
  • @Gremo 我已经编辑了我的答案,我给你的模式(神秘地)不起作用,但是这个会。
【解决方案2】:

根据您的常用搜索字符串和您的 PCRE 版本,preg_replacestrrpos 可能效果最好:

功能

function usingExplode($string, $replacement) {
    $pieces = explode('_', $string);
    array_pop($pieces);
    return implode('_', array_merge($pieces, array($replacement)));
}

function usingStrrpos($string, $replacement) {
    return substr($string, 0, strrpos($string, '_') + 1) . $replacement;
}

function usingPreg($string, $replacement) {
    return preg_replace('/_[^_]*$/', '_' . $replacement, $string);
}

测试工具

function speedTest($function, $string, $count = 100000) {
    $start = microtime(true);

    for ($i = 0; $i < $count; $i++) {
        $function($string, 'replacement');
    }

    $end = microtime(true);

    printf('%-12s: %01.2fs%s', $function, $end - $start, PHP_EOL);
}

$tests = array('a_b_c', 'abcdefghijklmnopqrstuvwxy_z', 'a_bcdefghijklmnopqrstuvwxyz', 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z');

foreach ($tests as $test) {
    echo $test . ':' . PHP_EOL;
    speedTest('usingExplode', $test);
    speedTest('usingStrrpos', $test);
    speedTest('usingPreg',    $test);
    echo PHP_EOL;
}

结果

a_b_c:
usingExplode: 0.64s
usingStrrpos: 0.34s
usingPreg   : 0.33s

abcdefghijklmnopqrstuvwxy_z:
usingExplode: 0.61s
usingStrrpos: 0.32s
usingPreg   : 0.32s

a_bcdefghijklmnopqrstuvwxyz:
usingExplode: 0.60s
usingStrrpos: 0.32s
usingPreg   : 0.32s

a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z:
usingExplode: 1.39s
usingStrrpos: 0.32s
usingPreg   : 0.71s

请注意(至少在我使用 PHP 5.4.0 的设置中),preg_replacestrrpos 竞争,直到最后一个下划线前面有大量下划线。

编辑:我将bfrohs's regex 插入套件,除非要替换的下划线靠近字符串的开头,否则效果不佳:

a_b_c:
usingPreg2: 0.40s

abcdefghijklmnopqrstuvwxy_z:
usingPreg2: 1.91s

a_bcdefghijklmnopqrstuvwxyz:
usingPreg2: 0.38s

a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z:
usingPreg2: 1.04s

【讨论】:

  • 使用/_[^_]+$/可能会获得更好的性能,这意味着您还需要使用"_$replacement"
  • 嗯,不知道为什么我首先包含子模式。我在没有它的情况下重新运行,它确实提高了最后一种情况(0.5s)的性能,但它仍然没有strrpos 快。无论如何,我已经删除了子模式。
【解决方案3】:

问题是第一个下划线匹配。你想要下划线后面的任何东西除了下划线:

'/_([^_]*)$/'

【讨论】:

    【解决方案4】:

    如果您关心速度,使用 strpos 将比 preg_replace 更快。所有字符串函数(据我所知)都比正则表达式函数慢。

    这是速度测试之一:http://lzone.de/articles/php-string-search.htm

    【讨论】:

      【解决方案5】:

      正则表达式/_(.*)$/ 匹配下划线,后跟任何文本,然后是字符串的结尾。没有什么可以阻止“任何文本”包含下划线,默认情况下,匹配器将选择最左边、最长的匹配项。所以在 'a_b_c' 中,它在 'a' 之后匹配。

      您可以通过将.(匹配任何字符)替换为字符类[^_](匹配任何字符除了下划线)来解决此问题。

      此外,由于您没有对捕获的组执行任何操作,因此不需要括号。而且,根据您的示例,由于您不想替换下划线本身,因此您应该将其排除在正则表达式之外。

      function bar($string, $replacement)
      {
          return preg_replace('/[^_]*$/', $replacement, $string, 1);
      }
      

      【讨论】:

      • 与真解相同的问题,preg_replace('/([^_]*)$/', '3', 'a_b_c') 得到了a_b_33
      • 啊,显然 preg_replace 默认为全局。所以你需要使用 [^_]+ 或 limit=1。由于@bfrohs 更新了他的答案来做前者,我更新了我的答案来做后者。
      【解决方案6】:

      '/([^_]+)$/' 匹配所有内容下划线最后一个下划线之后。

      注意:没有必要将下划线与此匹配。这样,您在替换时就不会丢失最后一个下划线。

      【讨论】:

      • preg_replace('/([^_]*)$/', '3', 'a_b_c') 我得到a_b_33,它使替换加倍...
      • @Gremo,抱歉,现已修复。问题在于没有将limitpreg_replace() 一起使用,因此它同时替换了c 和``(在字符串末尾)。因此,您可以使用我将答案更改为的+,或者在preg_replace() 中包含1 的限制。
      【解决方案7】:

      这看起来像一个表情符号,但我认为它应该可以工作:

      return preg_replace('/(.*_).*/', $replacement, $string);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-01
        • 2018-09-28
        • 2020-12-02
        • 1970-01-01
        相关资源
        最近更新 更多