【问题标题】:Java pattern match largest possible groupJava 模式匹配最大可能组
【发布时间】:2015-07-03 16:47:37
【问题描述】:

我有一个模式可以在字符串中找到重复的模式:

String patt = "(.+?)\\1+";

不幸的是,这会将“003003003”匹配为“0”。我需要修改什么才能使这个匹配为“003”?

【问题讨论】:

  • 您能否更准确地说明您需要执行此操作的确切上下文?因为这种方式非常有限(即使有更正的模式)。
  • 问题是“最大可能的组”,但如果您希望“000000”匹配“0”,那么这更像是重复形成整个字符串的最小可能组
  • 是否需要查找最左边的结果,是否允许重叠结果?
  • 如果你不希望这个量词不情愿,为什么要在+ 中添加?

标签: java regex regex-greedy


【解决方案1】:

只需删除问号,即使您的正则表达式变得贪婪。

Pattern patt = Pattern.compile("(.+)\\1+");

DEMO

为什么您的正则表达式在打印组索引 1 后只返回 0300

因为.+? 强制正则表达式引擎非贪婪地匹配任何字符一次或多次。所以这匹配第一个 0 并检查是否还有另一个或多个零或以下。是的,它就在那里。所以它捕获第一个零并匹配第二个零。

现在它需要第三个字符并进行检查。

使用锚点。

^(.+?)\\1+$

DEMO

【讨论】:

  • 这确实适用于这种情况。但如果我有“000000”,我会想要“0”。我不确定这是否可能。
  • 那么你必须使用锚。
【解决方案2】:

执行此操作的“理论”正则表达式可以是:

(?=(?<sub>(.+)(\\2+)))(?=(?<pattern>.+?)(?:\\4+\\3|\\2))

(在 DOTALL 模式下)

这个正则表达式将找到字符串中每个位置的最大重复子字符串。

我说“理论上的”是因为实际上你不能在长字符串中使用它,因为解析整个字符串所需的步骤数会随着字符串的大小而迅速增长。所以一个很好的折衷方案是限制搜索到的子串的大小,例如像这样:

(?=(?<sub>(.{1,20})(\\2+)))(?=(?<pattern>.{1,10}?)(?:\\4+\\3|\\2))

模式解释:

正则表达式分为两个前瞻断言,它们在同一位置测试字符串(因为前瞻断言是零宽度断言)。

第一个前瞻尝试找到一个子字符串(称为“子”),它是具有最大模式(第 2 组)的重复序列。这就是使用贪婪量词的原因。请注意,子字符串 (\\2+) 的结尾(在第一个模式之后)在第 3 组中被捕获,因为第二个前瞻将需要它。

第二次前瞻的目标是检查相同子字符串的最短模式是什么。这一次,使用了一个非贪婪的量词,当一个或多个新的更小的模式重复后到达第 3 组时,或者当达到最大的模式(在这种情况下没有更小的模式)时,前瞻成功。

图案细节:

(?= # first lookahead
    (?<sub>    # group "sub" (or group 1) with the whole substring
        (.+)   # group 2 with the largest pattern
        (\\2+) # group 3 with the end of the substring
    )
)
(?= # second lookahead
    (?<pattern>.+?) # the shortest pattern (group 4) ...
    (?:             # non-capturing group: ... must be followed by
        \\4+\\3     # one or more repetitions until the group 3
      |             # OR
        \\2         # until the largest pattern (group 2)
    )
)

演示:

import java.util.regex.*;

class repeatedSubstrings
{
    public static void main (String[] args) 
    {
        String s = "0070070071071071" + "\n"
                 + "Now is the winter of our discontent" + "\n"
                 + "Made glorious summer by this sun of York;" + "\n"
                 + "And all the clouds that lour'd upon our house" + "\n"
                 + "In the deep bosom of the ocean buried." + "\n"
                 + "Now are our brows bound with victorious wreaths;" + "\n"
                 + "Our bruised arms hung up for monuments;" + "\n"
                 + "Our stern alarums changed to merry meetings," + "\n"
                 + "Our dreadful marches to delightful measures." + "\n"
                 + "00000000" + "\n"
                 + "42424242";

        Pattern Reg = Pattern.compile("(?=(?<sub>(.+)(\\2+)))(?=(?<pattern>.+?)(?:\\4+\\3|\\2))", Pattern.DOTALL);

        Matcher m = Reg.matcher(s);

        int prevEnd = 0;
        int currEnd;

        long start = System.currentTimeMillis();

        StringBuilder display = new StringBuilder("substring      pattern   \tstart offset\tend offset\tlength\n");
        display.append("--------------------------------------------------------------------------------\n");

        while (m.find()) {
            currEnd = m.start() + m.group("sub").length();

            if (currEnd > prevEnd) {
                display.append(String.format("%-15s%-10s\t%-15d\t%-15d\t%d\n", m.group("sub"), m.group("pattern"), m.start(), currEnd, m.group("sub").length()));
                prevEnd = currEnd;
            }
        }

        long end = System.currentTimeMillis();

        System.out.println(display.toString());
        System.out.printf("Elapsed Time: %.1es\n", (end-start)/1000.0);
    }
}

注意:此正则表达式返回字符串中每个位置具有重复模式的最大子字符串。因此也会返回重叠的子字符串。对于字符串0070070071071071,子字符串是007007007071071071。如果您不希望这种行为,您可以简单地在正则表达式的末尾添加\\1 以获得007007007107107

示例代码不包括在前一个结果之后开始并在字符串中的相同偏移量之前或处结束的结果。如果您想获得所有这些结果,请删除 if 语句。

【讨论】:

    【解决方案3】:

    来自 OP 评论:But in case I have "000000" I would want "0"

    修改


    不同字符的重复组如果可能会首先匹配。

    事实证明(我相信)找到第一个不同的字符,
    导致最大的不同组被重复匹配。

    这使得可以使用非贪婪量词来实现这一点
    用于大型字符串而不会影响性能。

    解读结果:
    (注意 - 如果使用支持分支重置的引擎,所有结果都可以
    从同一组获得)

    第 0 组包含整个匹配项,那么;

    要么:

    • 第 1 组中不同字符的重复组(最长
    • 第 3 组中相同(单个)字符的重复组

    正则表达式:((.)+?(?!\2).+?)\1+|(.)\3+

    展开:

       (                # (1 start), Different chars (at least 2) repeating group
            ( . )+?          # (2), Shortest distance sample character
            (?! \2 )         # Border between different characters
            .+?              # First different, shortest distance
       )                # (1 end)
       \1+ 
    |                 # or
       ( . )            # (3), Same character repeating group
       \3+ 
    

    输入:

    000010910910901012222
    

    输出:

     **  Grp 0 -  ( pos 0 , len 4 ) 
    0000  
     **  Grp 1 -  NULL 
     **  Grp 2 -  NULL 
     **  Grp 3 -  ( pos 0 , len 1 ) 
    0  
    -------------
     **  Grp 0 -  ( pos 4 , len 9 ) 
    109109109  
     **  Grp 1 -  ( pos 4 , len 3 ) 
    109  
     **  Grp 2 -  ( pos 5 , len 1 ) 
    0  
     **  Grp 3 -  NULL 
    -------------
     **  Grp 0 -  ( pos 13 , len 4 ) 
    0101  
     **  Grp 1 -  ( pos 13 , len 2 ) 
    01  
     **  Grp 2 -  ( pos 13 , len 1 ) 
    0  
     **  Grp 3 -  NULL 
    -------------
     **  Grp 0 -  ( pos 17 , len 4 ) 
    2222  
     **  Grp 1 -  NULL 
     **  Grp 2 -  NULL 
     **  Grp 3 -  ( pos 17 , len 1 ) 
    2  
    

    使用分支重置:第 0 组 = 整个匹配,第 1 组 = 重复组。

     # (?|((.)+?(?!\2).+?)\1+|(.)\1+)
    
     (?|
          (                # (1 start), Different chars (at least 2) repeating group
               ( . )+?          # (2), Shortest distance sample character
               (?! \2 )         # Border between different characters
               .+?              # First different, shortest distance
          )                # (1 end)
          \1+ 
       |                 # or
          ( . )            # (1), Same character repeating group
          \1+ 
     )
    

    对重叠匹配使用单一前瞻。
    第 1 组 = 整场比赛,第 2 组 = 不同字符的重复组,第 4 组单个字符重复。

     # (?=(((.)+?(?!\3).+?)\2+|(.)\4+))
    
     (?=
          (                # (1 start), Capture entire match
               (                # (2 start), Different chars (at least 2) repeating group
                    ( . )+?          # (3), Shortest distance sample character
                    (?! \3 )         # Border between different characters
                    .+?              # First different, shortest distance
               )                # (2 end)
               \2+ 
            |                 # or
               ( . )            # (4), Same character repeating group
               \4+ 
          )                # (1 end)
     )
    

    使用 Branch Rreset 和单一前瞻来进行重叠匹配。
    第 1 组 = 整场比赛,第 2 组 = 重复组。

     # (?=((?|((.)+?(?!\3).+?)\2+|(.)\2+)))
    
     (?=
          (                # (1 start), Capture entire match
               (?|
                    (                # (2 start), Different chars (at least 2) repeating group
                         ( . )+?          # (3), Shortest distance sample character
                         (?! \3 )         # Border between different characters
                         .+?              # First different, shortest distance
                    )                # (2 end)
                    \2+ 
                 |                 # or
                    ( . )            # (2), Same character repeating group
                    \2+ 
               )
          )                # (1 end)
     )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-11
      相关资源
      最近更新 更多