PHP:从字符串的开头/结尾修剪单词或部分单词

时间:2014-03-26 07:22:31

标签: php regex string trim

我需要从字符串的开头和结尾修剪单词。问题是,有时候这些词可以缩写即。只有前三个字母(后跟点)。

我努力寻找合适的正则表达式。基本上我需要聊聊三个或更多的初始字符,直到更换的长度,但我找不到正则表达式,它将匹配可变长度并保持字符顺序。

例如,如果我需要从句子'insurance'修剪'insur. companies are rich',那么我会想到模式\^[insurance]{3,9}\,但这种模式也会捕捉'sensace'之类的字词,因为[]内的字符顺序(及其出现)对正则表达式并不重要。

此外,在字符串末尾,我需要删除序列号,这些序列号是从beginig缩写的 - 说'XK-25F14'有时会显示为'25F14'。所以我决定纯粹用角色进行角色比较。

因此我以下面的php函数结束

function trimWords($s, $dirt, $case_insensitive = false, $reverse = true)
{
    $pos = 0;
    $func = $case_insensitive ? 'strncasecmp' : 'strncmp';

    // Get number of initial characters, that match in both strings 
    while ($func($s, $dirt, $pos + 1) === 0)
        $pos++;

    // If more than 2 initial characters match, then remove the match   
    if ($pos > 2)
        $s = substr($s, $pos);

    // Reverse $s and $dirt so it will trim from the end of string
    $s = strrev($s);        
    if ($reverse)
        return trimWords($s, strrev($dirt), $case_insensitive, false);

    // After second run return back-reversed string 
    return trim($s, ' .-');
}

我对这个功能很满意,但它有一个缺点。它只修剪了一个单词。如何使其更容易修剪,即从'insurance '删除'Insurance insur. companies'

我也很好奇,它真的不存在这样的正则表达式,它会匹配变长并且会尊重模式中字符的顺序吗?

最终解决方案

感谢mrhobo 我已经使用基于正则表达式的函数结束了。这个功能可以很容易地改进,也是这项任务最有效的。

我修改了我以前的功能,它比regexp快两倍,但是每次运行只能删除一个单词,所以为了能够从开始和结束中删除单词,它必须自己运行两次,性能是与regexp相同,并且为了删除多个单词的出现,它必须多次运行,然后会越来越慢。

最终的功能是这样的。

function trimWords($string, $word, $case_insensitive = false, $min_abbrv = 3)
{
    $exc = substr($word, $min_abbrv);
    $pat = null;

    $i = strlen($exc);
    while ($i--)
        $pat = '(?>'.preg_quote($exc[$i], '#').$pat.')?';

    $pat = substr($word, 0, $min_abbrv).$pat;
    $pat = '#(?<begin>^)?(?:\W*\b'.$pat.'\b\W*)+(?(begin)|$)#';
    if ($case_insensitive)
        $pat .= 'i';

    return preg_replace($pat, '', $string);
}

注意:使用此功能无关紧要,如果缩写以点结尾,则会删除任何较短形式的单词,并删除单词周围的所有非单词字符。

编辑:我刚刚尝试创建像insu(r|ra|ran|ranc|rance)这样的替换模式,并且使用原子组的函数速度提高了约30%,而使用更长的单词则可能更高效。

2 个答案:

答案 0 :(得分:1)

在正则表达式中匹配单词和第n个字母中所有可能的缩写并不是一件非常简单的事。

以下是我将如何从第4个字母中为保险一词做这件事:

insu(?>r(?>a(?>n(?>c(?>(?<last>e))?)?)?)?)?(?(last)|\.)

http://regex101.com/r/aL2gV4

它的工作原理是使用原子组强制正则表达式引擎尽可能地向前推进最后一次&#39;使用嵌套模式(?>a(?>b)?)?的字母。如果匹配的是最后一个字母,则我们不处理缩写,因此不需要点,否则需要点。这由(?(last)|\.)编码。

Regular expression visualization

要修剪,我会创建一个函数来为缩写构建上述正则表达式。然后你可以编写一个while循环,用空格替换每个缩写正则表达式,直到没有更多的匹配。

非正则表达式

这是我的非正则表达式版本,它从字符串中删除多个单词和缩写词:

function trimWords($str, $word, $min_abbrv, $case_insensitive = false) {
  $len      = 0;
  $word_len = strlen($word);
  $strlen   = strlen($str);
  $cmp      = $case_insensitive ? strncasecmp : strncmp;

  for ($i = 0; $i < $strlen; $i++) {
    if ($cmp($str[$i], $word[$len], $i) == 0) {
      $len++;
    } else if ($len > 0) {
      if ($len == $word_len || ($len >= $min_abbrv && ($dot = $str[$i] == '.'))) {
        $i     -= $len;
        $len   += $dot;
        $str    = substr($str, 0, $i) . substr($str, $i+$len);
        $strlen = strlen($str);
        $dot    = 0;
      }
      $len = 0;
    }
  }

  return $str;
}

示例:

$string = 'ins. <- "ins." / insu. insuranc. insurance / insurance. <- "."';
echo trimWords($string, 'insurance', 4);

输出是:

ins. <- "ins." / / . <- "."

答案 1 :(得分:1)

我编写了根据 mrhobo 构建正则表达式模式的函数,并简单测试并使用纯PHP字符串比较对我的函数进行基准测试。

这是代码:     

$string = 'Insur. companies are nasty rich';
$dirt = 'insurance';
$cycles = 500000;


$start = microtime(true);

$i = $cycles;
while ($i) {
    $i--;
    regexpStyle($string, $dirt, true);
}

$stop = microtime(true);

$i = $cycles;
while ($i) {
    $i--;
    trimWords($string, $dirt, true);
}

$end = microtime(true);

$res1 = $stop - $start;
$res2 = $end - $stop;


$winner = $res1 < $res2 ? '<<<' : '>>>';

echo 'regexp: '.$res1.' '.$winner.' string operations: '.$res2;

function trimWords($s, $dirt, $case_insensitive = false, $reverse = true)
{
    $pos = 0;
    $func = $case_insensitive ? 'strncasecmp' : 'strncmp';

    // Get number of initial characters, that match in both strings 
    while ($func($s, $dirt, $pos + 1) === 0)
        $pos++;

    // If more than 2 initial characters match, then remove the match   
    if ($pos > 2)
        $s = substr($s, $pos);

    // After second run return back-reversed string 
    return trim($s, ' .-');
}

function regexpStyle($s, $dirt, $case_insensitive, $min_abbrev = 3)
{
    $ss = substr($dirt, $min_abbrev);
    $arr = str_split($ss);
    $patt = '(?>(?<last>'.array_pop($arr).'))?';
    $i = count($arr);
    while ($i)
        $patt = '(?>'.$arr[--$i].$patt.')?';
    $patt = '#^'.substr($dirt, 0, $min_abbrev).$patt.'(?(last)|\.)#';
    $patt .= $case_insensitive ? 'i' : null;
    return trim(preg_replace($patt, '', $s));
}

而胜利者是...沉默的时刻......它是......

平局

regexp: 8.5169589519501 >>> string operations: 8.0951890945435

但我强烈认为可以更好地利用正则表达式方法。