【问题标题】:Calculate with regex group matches?用正则表达式组匹配计算?
【发布时间】:2014-02-27 17:37:53
【问题描述】:

是否可以使用正则表达式组匹配进行计算?

字符串:

(00) Bananas
...
(02) Apples (red ones)
...
(05) Oranges
...
(11) Some Other Fruit
...

如果每行开头的数字之间的差为 3 或更少,则删除其间的“...”。所以字符串应该像这样返回:

(00) Bananas
(02) Apples (red ones)
(05) Oranges
...
(11) Some Other Fruit

正则表达式:

$match = '/(*ANYCRLF)\((\d+)\) (.+)$
\.{3}
\((\d+)\) (.+)/m';

现在棘手的部分是如何获取匹配项并将一些匹配项添加到类似

的条件中
if($3-$1 >= 3) {
  //replace
}

测试:http://codepad.viper-7.com/f6iI4m

谢谢!

【问题讨论】:

    标签: php regex


    【解决方案1】:

    以下是使用 preg_replace_callback() 的方法。

    $callback = function ($match) {
        if ($match[3] <= $match[2] + 3) {
            return $match[1];
        } else {
            return $match[0];
        }
    };
    
    $newtxt = preg_replace_callback('/(^\((\d+)\).+$)\s+^\.{3}$(?=\s+^\((\d+)\))/m', $callback, $txt);
    
    /(^\((\d+)\).+$)\s+^\.{3}$(?=\s+^\((\d+)\))/m
    

    这是碎片的模式:

    (^\((\d+)\).+$)      # subpattern 1, first line; subpattern 2, the number
    \s+^\.{3}$           # newline(s) and second line ("...")
    (?=\s+^\((\d+)\))    # lookahead that matches another numbered line 
                         # without consuming it; contains subpattern 3, next number
    

    因此,整个模式的匹配是前两行(即编号行和'...'行)。

    如果数字差大于3,则替换为$match[0]中的原文(即无变化)。如果差异小于或等于 3,则仅替换为第一行(在 $match1] 中找到)。

    【讨论】:

    • @Martin 很高兴听到!我不确定是否还有其他我没有考虑到的情况,例如连续多个“...”行。如果您确实发现了其他需要说明的情况,我很乐意进行修改。
    【解决方案2】:

    您可以使用preg_replace_callback 并使用任何php 代码返回替换字符串,回调接收捕获。但是,对于您的输出,您必须获得重叠匹配以进行替换:

    1. 比较(00) Bananas(02) Apples -> 2-0=2 替换
    2. 比较(02) Apples(05) Oranges -> 5-2=3 替换
    3. ...

    但由于输入的 (02) Apples 部分已用于上一场比赛,因此您不会第二次拾起它。

    编辑:

    这是一个基于正则表达式的超前解决方案,归功于 Wiseguy:

    $s = "(00) Bananas
    ...
    (02) Apples (red ones)
    ...
    (05) Oranges
    ...
    (11) Some Other Fruit
    ...";
    
    $match = '/(*ANYCRLF)\((\d+)\) (.+)$
    \.{3}
    (?=\((\d+)\) (.+))/m';
    
    // php5.3 anonymous function syntax
    $s = preg_replace_callback($match, function($m){
        if ($m[3] - $m[1] <= 3) {
            print preg_replace("/[\r\n]+.../", '', $m[0]);
        } else {
            print $m[0];
        }
    }, $s);
    echo $s;
    

    这是我的第一次拍摄,基于“找到点然后查看上一行/下一行”的逻辑:

    $s = "(00) Bananas
    ...
    (02) Apples (red ones)
    ...
    (05) Oranges
    ...
    (11) Some Other Fruit
    ...
    (18) Some Other Fruit
    ...
    (19) Some Other Fruit
    ...
    ";
    
    $s = preg_replace("/[\r\n]{2}/", "\n", $s);
    
    $num_pattern = '/^\((?<num>\d+)\)/';
    $dots_removed = 0;
    
    preg_match_all('/\.{3}/', $s, $m, PREG_OFFSET_CAPTURE);
    foreach ($m[0] as $i => $dots) {
        $offset = $dots[1] - ($dots_removed * 4); // fix offset of changing input
    
        $prev_line_end = $offset - 2; // -2 since the offset is pointing to the first '.', prev char is "\n"
        $prev_line_start = $prev_line_end; // start the search for the prev line's start from its end
        while ($prev_line_start > 0 && $s[$prev_line_start] != "\n") {
            --$prev_line_start;
        }
    
        $next_line_start = $offset + strlen($dots[0]) + 1;
        $next_line_end = strpos($s, "\n", $next_line_start);
    $next_line_end or $next_line_end = strlen($s);
    
        $prev_line = trim(substr($s, $prev_line_start, $prev_line_end - $prev_line_start));
        $next_line = trim(substr($s, $next_line_start, $next_line_end - $next_line_start));
    
        if (!$next_line) {
            break;
        }
    
        // get the numbers
        preg_match($num_pattern, $prev_line, $prev);
        preg_match($num_pattern, $next_line, $next);
    
        if (intval($next['num']) - intval($prev['num']) <= 3) {
            // delete the "..." line
            $s = substr_replace($s, '', $offset-1, strlen($dots[0]) + 1);
            ++$dots_removed;
        }
    }
    
    print $s;
    

    【讨论】:

    • 感谢您的尝试。那么暴力解决方案呢?
    • 添加了我对这个问题的看法。
    • 其实,你可以使用preg_replace_callback(),但是对下一行使用前瞻,这样就不会被消耗掉。
    • complex857,当字符串不以“...”结尾时不起作用
    • @Martin:当最后一行不是“\n”终止时失败。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-14
    • 1970-01-01
    • 2019-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多