【问题标题】:Match contents within square brackets, including nested square brackets匹配方括号内的内容,包括嵌套的方括号
【发布时间】:2015-10-17 15:07:54
【问题描述】:

我正在尝试编写一个剧透识别系统,以便将字符串中的任何剧透替换为指定的剧透字符。

我想匹配一个用方括号括起来的字符串,这样方括号内的内容就是捕获组 1,包括周围括号的整个字符串就是匹配项。

我目前正在使用\[(.*?]*)\],对这个答案here 中的表达式稍作修改,因为我还希望嵌套方括号成为捕获组 1 的一部分。

该表达式的问题在于,尽管它可以工作并匹配以下内容:

  • Jim ate a [sandwich] 匹配 [sandwich]sandwich 作为组 1
  • Jim ate a [sandwich with [pickles and onions]] 匹配 [sandwich with [pickles and onions]]sandwich with [pickles and onions] 作为组 1
  • [[[[] 匹配 [[[[][[[ 作为组 1
  • []]]] 匹配 []]]]]]] 作为组 1

但是,如果我想匹配以下内容,它不会按预期工作:

  • Jim ate a [sandwich with [pickles] and [onions]] 匹配两者:
    • [sandwich with [pickles]sandwich with [pickles 作为第 1 组
    • [onions]]onions] 作为第 1 组

我应该使用什么表达式来匹配[sandwich with [pickles] and [onions]]sandwich with [pickles] and [onions] 作为组1?

编辑

由于在 Java 中使用正则表达式似乎不可能实现这一点,是否有替代解决方案?

编辑 2

我还希望能够通过找到的每个匹配项来拆分字符串,因此由于String.split(regex) 很方便,因此正则表达式的替代方案将更难实现。这是一个例子:

  • Jim ate a [sandwich] with [pickles] and [dried [onions]] 匹配所有:
    • [sandwich]sandwich 作为第 1 组
    • [pickles]pickles 作为第 1 组
    • [dried [onions]]dried [onions] 作为第 1 组

拆分后的句子应该是这样的:

Jim ate a
with
and

【问题讨论】:

  • 使用 java 或 javascript 正则表达式是不可能的。
  • 我能解释一下为什么这是不可能的吗?问题是针对正则表达式,而不是 Java 或 Javascript 正则表达式。你是在告诉我其他语言也可以吗?
  • 是的,正则表达式引擎在语言之间是不同的,这就是为什么在没有使用的语言或应用程序的情况下提出正则表达式问题是没有意义的。要匹配未知级别的嵌套括号,您需要递归功能(PCRE、Perl 中提供)或平衡组功能(.net 中提供)。 Java 和 javascript 没有这些特性之一。
  • 谢谢。我将寻求替代解决方案。
  • 替代方案很简单:构建自己的逐个字符遍历的解析器,使用“堆栈”变量,当 char 是左括号时,递增它,当 char 是右括号时递减它.当堆栈为零时,括号是平衡的。

标签: java regex


【解决方案1】:

更直接的解决方案

This solution 将省略空或仅空格的子字符串

public static List<String> getStrsBetweenBalancedSubstrings(String s, Character markStart, Character markEnd) {
    List<String> subTreeList = new ArrayList<String>();
    int level = 0;
    int lastCloseBracket= 0;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
            if (c == markStart) {
                    level++;
                    if (level == 1 && i != 0 && i!=lastCloseBracket &&
                        !s.substring(lastCloseBracket, i).trim().isEmpty()) {
                            subTreeList.add(s.substring(lastCloseBracket, i).trim());
                }
            }
        } else if (c == markEnd) {
            if (level > 0) { 
                level--;
                lastCloseBracket = i+1;
            }
            }
    }
    if (lastCloseBracket != s.length() && !s.substring(lastCloseBracket).trim().isEmpty()) {
        subTreeList.add(s.substring(lastCloseBracket).trim());  
    }
    return subTreeList;
}

然后,将其用作

String input = "Jim ate a [sandwich][ooh] with [pickles] and [dried [onions]] and ] [an[other] match] and more here";
List<String> between_balanced =  getStrsBetweenBalancedSubstrings(input, '[', ']');
System.out.println("Result: " + between_balanced);
// => Result: [Jim ate a, with, and, and ], and more here]

原始答案(更复杂,显示了一种提取嵌套括号的方法)

您还可以提取平衡括号内的所有子字符串,然后用它们拆分:

String input = "Jim ate a [sandwich] with [pickles] and [dried [onions]] and ] [an[other] match]";
List<String> balanced = getBalancedSubstrings(input, '[', ']', true);
System.out.println("Balanced ones: " + balanced);
List<String> rx_split = new ArrayList<String>();
for (String item : balanced) {
    rx_split.add("\\s*" + Pattern.quote(item) + "\\s*");
}
String rx = String.join("|", rx_split);
System.out.println("In-betweens: " + Arrays.toString(input.split(rx)));

这个函数会找到所有[]-balanced子串:

public static List<String> getBalancedSubstrings(String s, Character markStart, 
                                     Character markEnd, Boolean includeMarkers) {
    List<String> subTreeList = new ArrayList<String>();
    int level = 0;
    int lastOpenBracket = -1;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == markStart) {
            level++;
            if (level == 1) {
                lastOpenBracket = (includeMarkers ? i : i + 1);
            }
        }
        else if (c == markEnd) {
            if (level == 1) {
                subTreeList.add(s.substring(lastOpenBracket, (includeMarkers ? i + 1 : i)));
            }
            if (level > 0) level--;
        }
    }
    return subTreeList;
}

IDEONE demo

代码执行结果:

Balanced ones: ['[sandwich], [pickles], [dried [onions]]', '[an[other] match]']
In-betweens: ['Jim ate a', 'with', 'and', 'and ]']

致谢:getBalancedSubstrings 是基于peter.murray.rustHow to split this “Tree-like” string in Java regex? post 的回答。

【讨论】:

  • 这是一个可行的选择,适合我的需要。谢谢!
  • 我增强了getBalancedSubstrings 方法以允许传递开始/结束单个字符标记和一个标志以返回带有和不带有标记的子字符串。
猜你喜欢
  • 1970-01-01
  • 2011-01-07
  • 1970-01-01
  • 2019-10-04
  • 2018-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
相关资源
最近更新 更多