【问题标题】:REGEX capture each n-letters words between two words of a sentence正则表达式捕获句子的两个单词之间的每个 n 字母单词
【发布时间】:2020-11-01 03:06:41
【问题描述】:

我很难在一个句子的两个单词之间只选择 n 长度的单词: 前任: 对于声明: "这是开始一些单词要被选择结束不再选择"

假设我想在单词“start”和“end”之间选择 3 个以上的单词,结果将捕获 一些,单词,被选中忽略 to 和 be。

https://regex101.com/r/Ost7Wn/3

仅选择 [\w]{3,} 本身就可以工作,但我不知道如何将它放在句子中的“开始”和“结束”这两个词之间,以匹配我出现的 n 字母词只在他们之间。我已经尝试了很多东西,从环视到捕获组,但我真的无法做到!

有什么想法吗?谢谢

【问题讨论】:

    标签: regex regex-lookarounds


    【解决方案1】:

    您可以将此正则表达式与前瞻和\G 一起使用:

    (?:\bSTART\b|(?!^)\G)\h+(?!END\b).*?\b(\w{3,})(?=.*?\bEND\b)
    

    RegEx Demo

    正则表达式详细信息:

    • (?:\bSTART\b|(?!^)\G):匹配单词 START 或从上一个匹配的结尾开始匹配 0 个或多个以 1+ 空格分隔的单词。
    • \G: 在上一个匹配的结尾或第一个匹配的字符串的开头断言位置
    • \h+(?!END\b).*?(\w{4,}): 匹配 1+ 个空格,后跟 0 个或多个字符,后跟 4+ 个长度的单词,在组 #1 中捕获
    • (?=.*?\bEND\b): 向前断言单词 END 的存在

    【讨论】:

    • 是的,感谢您的贡献。以前从未听说过 \G,正则表达式很迷人!
    • 顺便说一句,我在你的正则表达式中找到了一些东西 :( 如果你在开始的旁边放了一个非 4 字母,它就不再匹配了......
    • 酷,它现在完美运行。你能解释一下区别吗?
    • @anubhava 如果您有多个 END 锚点,例如 START one two three END one two END 将匹配 one, two, END, one, two,则会失败
    • 问题在于单词,标点符号不是问题的一部分,但是如果将来需求发生变化,这个正则表达式可以很容易地修改。
    【解决方案2】:

    如果支持后向中的量词,您也可以使用

    (?<=\bSTART\s+(?:\w+\s+)*?)\w{3,}(?=(?:\s+\w+)*?\s+END\b)
    

    说明

    • (?&lt;= 正面向后看,断言左边是
      • \bSTART\s+(?:\w+\s+)*? 匹配 START 可选地按单词和空格字符重复
    • ) 近距离观察
    • \w{3,} 匹配 3 个或更多单词字符
    • (?= 正向前瞻,断言右边是什么
      • (?:\s+\w+)*?\s+END\b 可选择重复空格和单词字符并匹配 END
    • ) 关闭前瞻

    Regex demo

    【讨论】:

    • 哇,非常感谢,经过几个小时,我终于明白我的错误了!我没有在我的后视和前瞻中添加可选的“非贪婪”搜索词!现在已经很清楚了,非常感谢!
    • 太糟糕了,Java 不支持量化的后视? :(
    • @Carl Verret 是,但是您必须指定一个有限量词而不是无限量词。你可以试试(?&lt;=\bSTART\s{1,10}(?:\w+\s{1,10}){0,1000})\w{3,}(?=(?:\s+\w+)*?\s+END\b)regex101.com/r/MfNElT/1
    • 你是对的!但 {0,1000} 在另一边
    • 尽量不要贪心{0,1000}?
    【解决方案3】:

    这是一个有趣的场景。通常,您最好从主源中提取字符串 start(.*)end,然后在子字符串上运行 RegEx。

    但这并不意味着不可能使用一个 RegEx!

    我确定您已经为 positive|negative lookahead|lookbehind 苦苦挣扎,并发现您无法进行动态长度后视,这真的很麻烦,例如(?&lt;=start.*)lookahead 一样。

    对于此示例,您必须了解的关键是 RegEx 在匹配时将 cursor 位置移动到字符串中...这是我们将用来完成这项工作的警告。

    正则表达式

    (?:.*start|^.*|end.*)|\b(\w{3,})(?=.*end)
    (?:                                         : Start of non-capture group
       .*start                                  : [Match pattern 1a] matches anything upto and including the {start} anchor
              |                                 : OR operator
               ^.*                              : [Match pattern 1b] matches from the {^} start of the string to the end
                  |                             : OR operator
                   end.*                        : [Match pattern 1c] matches from the {end} anchor to the end of the string
                        )                       : End of non-capture group
                         |                      : OR operator
                          \b(\w{3,})(?=.*end)   : Captures a boundary [\b] followed by word characters [a-zA-Z0-9_] 3 or more times whilst using a positive lookahead to check that the {end} anchor hasn't been passed
    

    冗长的解释

    上面的RegEx可以写成,简单来说:

        NON-CAPTURING_GROUP OR CAPTURING_GROUP
    OR, more verbose
        (MATCH_PATTERN_1a OR MATCH_PATTERN_1b OR MATCH_PATTERN_1c) OR MATCH_PATTERN_2
    
    • NON-CAPTURING_GROUP 总是首先被评估,所以我们在这里检查我们实际上想要得到匹配
      • MATCH_PATTERN_1a 检查 start 锚点是否存在,并将 cursor 移动到字符串中的那个点
      • MATCH_PATTERN_1b 仅在 1a 失败且字符串中存在 start 锚点时匹配。如果是这样,它匹配所有内容并且表达式停止。
      • MATCH_PATTERN_1c 检查未到达 end 锚点。如果匹配,则匹配到字符串的末尾并且表达式停止。
    • CAPTURING_GROUP 总是排在第二位;所以只匹配如果它应该
      • MATCH_PATTERN_2 匹配任何单词边界,后跟单词字符 [a-zA-Z0-9_] 在指定长度之间
        • 它还会检查 positive lookahead 以确保 end 锚未通过

    警告

    请注意,第一个和最后一个捕获将始终来自 NON-CAPTURE 组,应该被忽略。根据 regex 的实现方式,它可能是空的、完整的匹配字符串或两者(多维数组)。

    示例 [Python]

    注意:Python$result = $full_matches[] 格式输出 注意:flag = re.I 已设置为使 RegEx 不区分大小写,即它匹配 STARTstart

    import re
    
    test_str1 = """one two three four START four five two five six END seven"""
    test_str2 = """this is the start some words are to be selected end no more select"""
    test_str3 = """these are some words that shouldn't be selected end also not selected"""
    test_str4 = """end two four five two five six END seven"""
    test_str5 = """one start two three end four five six end seven one"""
    test_str6 = """END START two four five two five six seven"""
    
    regex1 = r"(?:.*start|^.*|end.*)|\b(\w{3,})(?=.*end)"
    regex2 = r"(?:.*start|^.*|end.*)|\b(\w{4,})(?=.*end)"
    
    print(re.findall(regex1, test_str1, re.I))
    print(re.findall(regex1, test_str2, re.I))
    print(re.findall(regex1, test_str3, re.I))
    print(re.findall(regex1, test_str4, re.I))
    print(re.findall(regex1, test_str5, re.I))
    print(re.findall(regex1, test_str6, re.I))
    
    print(re.findall(regex2, test_str1, re.I))      
    print(re.findall(regex2, test_str2, re.I))
    print(re.findall(regex2, test_str3, re.I))
    print(re.findall(regex2, test_str4, re.I))
    print(re.findall(regex2, test_str5, re.I))
    print(re.findall(regex2, test_str6, re.I))
    
    '''
      Output:
        ['', 'four', 'five', 'two', 'five', 'six', '']
        ['', 'some', 'words', 'are', 'selected', '']
        ['']
        ['']
        ['', 'two', 'three', '']
        ['']
        ['', 'four', 'five', 'five', '']
        ['', 'some', 'words', 'selected', '']
        ['']
        ['']
        ['', 'three', '']
        ['']
    '''
    

    示例 [PHP]

    注意:PHP$result = [$full_matches[], $capture_group[]] 格式输出 注意:flag = i 已设置为使 RegEx 不区分大小写,即它匹配 STARTstart

    $test_str1 = "one two three four START four five two five six END seven";
    $test_str2 = "this is the start some words are to be selected end no more select";
    $test_str3 = "these are some words that shouldn't be selected end also not selected";
    $test_str4 = "end two four five two five six END seven";
    $test_str5 = "one start two three end four five six end seven one";
    $test_str6 = "END START two four five two five six seven";
    
    $regex1 = "/(?:.*start|^.*|end.*)|\b(\w{3,})(?=.*end)/i";
    $regex2 = "/(?:.*start|^.*|end.*)|\b(\w{4,})(?=.*end)/i";
    
    preg_match_all($regex1, $test_str1, $matches1);
    preg_match_all($regex1, $test_str2, $matches2);
    preg_match_all($regex1, $test_str3, $matches3);
    preg_match_all($regex1, $test_str4, $matches4);
    preg_match_all($regex1, $test_str5, $matches5);
    preg_match_all($regex1, $test_str6, $matches6);
    
    preg_match_all($regex2, $test_str1, $matches7);
    preg_match_all($regex2, $test_str2, $matches8);
    preg_match_all($regex2, $test_str3, $matches9);
    preg_match_all($regex2, $test_str4, $matches10);
    preg_match_all($regex2, $test_str5, $matches11);
    preg_match_all($regex2, $test_str6, $matches12);
    
    echo json_encode($matches1);
    echo "\n";
    echo json_encode($matches2);
    echo "\n";
    echo json_encode($matches3);
    echo "\n";
    echo json_encode($matches4);
    echo "\n";
    echo json_encode($matches5);
    echo "\n";
    echo json_encode($matches6);
    echo "\n";
    echo json_encode($matches7);
    echo "\n";
    echo json_encode($matches8);
    echo "\n";
    echo json_encode($matches9);
    echo "\n";
    echo json_encode($matches10);
    echo "\n";
    echo json_encode($matches11);
    echo "\n";
    echo json_encode($matches12);
    
    /*
      Output:
        [["one two three four START","four","five","two","five","six","END seven"],["","four","five","two","five","six",""]]
        [["this is the start","some","words","are","selected","end no more select"],["","some","words","are","selected",""]]
        [["these are some words that shouldn't be selected end also not selected"],[""]]
        [["end two four five two five six END seven"],[""]]
        [["one start","two","three","end four five six end seven one"],["","two","three",""]]
        [["END START"],[""]]
        [["one two three four START","four","five","five","END seven"],["","four","five","five",""]]
        [["this is the start","some","words","selected","end no more select"],["","some","words","selected",""]]
        [["these are some words that shouldn't be selected end also not selected"],[""]]
        [["end two four five two five six END seven"],[""]]
        [["one start","three","end four five six end seven one"],["","three",""]]
        [["END START"],[""]]
    */
    

    .NET

    如果您使用的是.NET,那么 RegEx 会变得简单得多:

    start(\s*(?!end)\w+\s*)*end
    

    这是因为.NET 允许您捕获所有出现的回避字符串。

    其他方法

    实际上,您最好将字符串拆分为子字符串并从那里进行评估...

    输入

    start one two three end start one two three end one two end
    

    拆分字符串

    start(.*?)end
    
    [0] => start one two three end
    [1] => start one two three end one two end
    

    匹配单词

    \b\w{3,}
    

    示例

    $string = "start one two three end start four five six end one two end";
    
    preg_match_all('/start(.*?)end/i', $string, $matches);
    
    foreach($matches[1] as $match){
      preg_match_all('/\b\w{3,}/', $match, $out);
      var_dump($out);
    }
    
    /*
      Output:
        array(1) {
          [0]=>
          array(3) {
            [0]=>
            string(3) "one"
            [1]=>
            string(3) "two"
            [2]=>
            string(5) "three"
          }
        }
        array(1) {
          [0]=>
          array(3) {
            [0]=>
            string(4) "four"
            [1]=>
            string(4) "five"
            [2]=>
            string(3) "six"
          }
        }
    */
    

    【讨论】:

    • 这是错误的,因为即使没有 start 单词,它也会打印相同的输出,例如four five two five six END seven
    • 感谢您的解释和时间,但不幸的是,如果您删除起始词,它甚至会捕获这些词。不得不接受另一个回应。
    • @anubhava 我不确定你的意思,从哪里删除起始词?它肯定不会捕获结束后的单词;如果没有 start 单词,它将从字符串的开头捕获单词
    • @anubhava 好吧,我想你是对的......我曾假设正在搜索的数据是相关的。然而,这是一个足够简单的修复
    • @anubhava 我刚刚这样做了,对我来说效果很好?? (假设您正确设置了标志,即i
    猜你喜欢
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多