首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于匹配任意长度的所有重复子字符串的Regex表达式

用于匹配任意长度的所有重复子字符串的Regex表达式
EN

Stack Overflow用户
提问于 2012-12-14 07:52:16
回答 4查看 2.4K关注 0票数 3

假设我们有一个字符串:"abcbcdcde"

我想用regex来识别在这个字符串中重复的所有子字符串(即没有强制迭代循环)。

对于上面的字符串,结果集是:{"b“、"bc”、"c“、"cd”、"d"}

我必须承认,对有我经验的人来说,我的判罚要生疏得多。我试过使用反向引用,但这只会匹配连续的副本。我需要匹配所有的副本,无论是连续还是其他。

换句话说,我想匹配>=第二次出现的任何字符。如果一个子字符串发生了5次,那么我想要捕获每个事件2-5。讲得通?

到目前为止,这是我可悲的尝试:

代码语言:javascript
复制
preg_match_all( '/(.+)(.*)\1+/', $string, $matches );  // Way off!

我试着玩看头,但我只是在屠杀它。我是用PHP (PCRE)来做这件事的,但问题或多或少与语言无关。我发现自己被困在这件事上有点尴尬。

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2012-12-14 08:20:30

你的问题是递归..。您知道吗,忘了递归!=p --它在PHP中不能很好地工作,而且没有它的算法也很清晰。

代码语言:javascript
复制
  function find_repeating_sequences($s)
  {
    $res = array();
    while ($s) {
        $i = 1; $pat = $s[0];
        while (false !== strpos($s, $pat, $i)) {
            $res[$pat] = 1;
            // expand pattern and try again
            $pat .= $s[$i++];
        }
        // move the string forward
        $s = substr($s, 1);
    }
    return array_keys($res);
  }

出于兴趣,我还用PHP编写了Tim's answer

代码语言:javascript
复制
function find_repeating_sequences_re($s)
{
    $res = array();
    preg_match_all('/(?=(.+).*\1)/', $s, $matches);
    foreach ($matches[1] as $match) {
        $length = strlen($match);
        if ($length > 1) {
            for ($i = 0; $i < $length; ++$i) {
                for ($j = $i; $j < $length; ++$j) {
                    $res[substr($match, $i, $j - $i + 1)] = 1;
                }
            }
        } else {
            $res[$match] = 1;
        }
    }
    return array_keys($res);
}

我让他们在800字节的随机数据的基准测试中解决了这个问题:

代码语言:javascript
复制
$data = base64_encode(openssl_random_pseudo_bytes(600));

每个代码运行10轮,并测量执行时间。结果呢?

代码语言:javascript
复制
Pure PHP      - 0.014s (10 runs)
PCRE          - 40.86s <-- ouch!

当您查看24k字节(或任何超过1k的字节)时,情况会变得更加奇怪:

代码语言:javascript
复制
Pure PHP      - 4.565s (10 runs)
PCRE          - 0.232s <-- WAT?!

结果表明,正则表达式在1k字符之后崩溃,因此$matches数组为空。这些是我的.ini设置:

代码语言:javascript
复制
pcre.backtrack_limit => 1000000 => 1000000
pcre.recursion_limit => 100000 => 100000

在我看来还不清楚回溯或递归限制是如何在1k字符之后达到的。但是即使这些设置是“固定的”,结果仍然是显而易见的,PCRE似乎不是答案。

我想用C写这个会在一定程度上加快速度,但我不确定到了什么程度。

更新

hakre's answer的帮助下,我编写了一个经过优化后性能提高了18%的改进版本:

  1. 删除外部循环中的substr()调用,以推进字符串指针;这是我以前的递归版本遗留下来的。
  2. 将部分结果用作正缓存,以跳过内部循环中的strpos()调用。

在这里,在它的所有荣耀(:

代码语言:javascript
复制
function find_repeating_sequences3($s)
{
    $res = array(); 
    $p   = 0;
    $len = strlen($s);

    while ($p != $len) {
        $pat = $s[$p]; $i = ++$p;
        while ($i != $len) {
            if (!isset($res[$pat])) {
                if (false === strpos($s, $pat, $i)) {
                    break;
                }
                $res[$pat] = 1;
            }
            // expand pattern and try again
            $pat .= $s[$i++];
        }
    }
    return array_keys($res);
}
票数 9
EN

Stack Overflow用户

发布于 2012-12-14 08:19:20

无法在单个正则表达式中获得所需的结果,因为正则表达式要么贪婪地匹配(查找bc...bc),要么懒惰地匹配(查找b...bc...c),但两者都不匹配。(在您的示例中,它确实找到了c...c,但这只是因为c被重复了两次。)

但是,一旦找到长度大于1的重复子字符串,那么所有较小的“该子字符串的子字符串”也必须重复。如果你想把它们拼出来,你需要分开做。

以您的例子为例(使用Python,因为我不知道PHP):

代码语言:javascript
复制
>>> results = set(m.group(1) for m in re.finditer(r"(?=(.+).*\1)", "abcbcdcde"))
>>> results
{'d', 'cd', 'bc', 'c'}

然后,您可以将以下函数应用于每个结果:

代码语言:javascript
复制
def substrings(s):
    return [s[start:stop] for start in range(len(s)-1) 
                          for stop in range(start+1, len(s)+1)]

例如:

代码语言:javascript
复制
>>> substrings("123456")
['1', '12', '123', '1234', '12345', '123456', '2', '23', '234', '2345', '23456',
 '3', '34', '345', '3456', '4', '45', '456', '5', '56']
票数 2
EN

Stack Overflow用户

发布于 2012-12-14 07:57:43

我能得到的最接近的是/(?=(.+).*\1)/

查找的目的是允许多次匹配相同的字符(例如,ccd)。然而,由于某些原因,它似乎没有得到b.

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/13874663

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档