用于分离括号内字符串的正则表达式

11

我有一个包含2或3个公司名称的字符串,每个名称都用括号括起来。每个公司名称也可以包含括号中的单词。我需要使用正则表达式将它们分开,但找不到方法。

我的inputStr

(Motor (Sport) (racing) Ltd.) (Motorsport racing (Ltd.)) (Motorsport racing Ltd.)
or 
(Motor (Sport) (racing) Ltd.) (Motorsport racing (Ltd.))
预期结果是:

str1 = Motor (Sport) (racing) Ltd.
str2 = Motorsport racing (Ltd.)
str3 = Motorsport racing Ltd.

我的代码:

String str1, str2, str3;
Pattern p = Pattern.compile("\\((.*?)\\)");
Matcher m = p.matcher(inputStr);
int index = 0;
while(m.find()) {

    String text = m.group(1);
    text = text != null && StringUtils.countMatches(text, "(") != StringUtils.countMatches(text, ")") ? text + ")" : text;

    if (index == 0) {
        str1= text;
    } else if (index == 1) {
        str2 = text;
    } else if (index == 2) {
        str3 = text;
    }

    index++;
}

这对于str2str3非常有效,但对于str1则不行。

当前结果:

str1 = Motor (Sport)
str2 = Motorsport racing (Ltd.)
str3 = Motorsport racing Ltd.

1
尝试使用\(((?:[^()]+|\([^\)]*\))*)\)。实时演示(匹配在右侧):https://regex101.com/r/ppnfjy/1 - revo
3
不应该使用正则表达式处理嵌套结构。但如果真的需要处理,可以参考这里:https://dev59.com/d1YN5IYBdhLWcg3wm5Si - Erwin Bolwidt
@revo 你是吗? - Kevin Anderson
@ErwinBolwidt,你所指的嵌套结构并不是正则表达式难以处理的模式。对于引文中的模式,引擎匹配起来非常简单。即使是古老的 POSIX BRE 也可以做到。请阅读您上面的评论。 - revo
@revo,你有预测OP需求的水晶球吗?它没有指定嵌套级别的限制。 - Erwin Bolwidt
显示剩余5条评论
4个回答

9
您可以在不使用正则表达式的情况下解决此问题;请参考有关如何查找最外层括号的此问题

以下是示例:

import java.util.Stack;

public class Main {

    public static void main(String[] args) {
        String input = "(Motor (Sport) (racing) Ltd.) (Motorsport racing (Ltd.)) (Motorsport racing Ltd.)";
        for (int index = 0; index < input.length(); ) {
            if (input.charAt(index) == '(') {
                int close = findClose(input, index);  // find the  close parentheses
                System.out.println(input.substring(index + 1, close));
                index = close + 1;  // skip content and nested parentheses
            } else {
                index++;
            }
        }
    }
    private static int findClose(String input, int start) {
        Stack<Integer> stack = new Stack<>();
        for (int index = start; index < input.length(); index++) {
            if (input.charAt(index) == '(') {
                stack.push(index);
            } else if (input.charAt(index) == ')') {
                stack.pop();
                if (stack.isEmpty()) {
                    return index;
                }
            }
        }
        // unreachable if your parentheses is balanced
        return 0;
    }

}

输出:

Motor (Sport) (racing) Ltd.
Motorsport racing (Ltd.)
Motorsport racing Ltd.

2
@ifloop 即使方法B比方法A更有效率? - xingbin
是的,你一直把建议和答案混淆了。如果问题是“做xyz的最佳/最有效的方法是什么”,或者如果OP添加了“还有更好/更简单/更高效的方法吗”,那么你的贡献将被归类为答案。 - ifloop
6
从语法层面上来说,你是正确的。但从解决问题的角度来看,我不同意。替代方法很有用。你可能想查看这个关于SO不友好的元帖子:https://meta.stackoverflow.com/questions/366692/how-do-you-know-stack-overflow-feels-unwelcoming - Tamas Rev
1
@ifloop 你说得对,除了如果原帖作者不想要任何不同的方法,他会提到这一点。 - Napstablook
1
不同的解决问题方法可以拓宽你的视野,帮助你更好地理解问题。如果这种替代方法不能解决原帖作者的问题,他可以轻松地在回复中提到。 - Napstablook
显示剩余2条评论

8

所以我们可以假设括号最多只能嵌套两层。因此,我们可以轻松地完成它,不需要太多的技巧。我会选择以下代码:

List<String> matches = new ArrayList<>();
Pattern p = Pattern.compile("\\([^()]*(?:\\([^()]*\\)[^()]*)*\\)");
Matcher m = p.matcher(inputStr);
while (m.find()) {
    String fullMatch = m.group();
    matches.add(fullMatch.substring(1, fullMatch.length() - 1));
}

解释:

  • 首先我们匹配一个括号:\\(
  • 然后我们匹配一些非括号字符:[^()]*
  • 然后零次或多次:(?:...)* 我们会看到一些括号内的东西,然后再次出现一些非括号内容:
  • \\([^()]*\\)[^()]* - 很重要的是我们不允许在内部括号中再出现任何括号
  • 然后闭合括号出现了:\\)
  • m.group(); 返回实际的完整匹配。
  • fullMatch.substring(1, fullMatch.length() - 1) 移除了开头和结尾的括号。你也可以用另一个组来实现。我只是不想让正则表达式变得更丑陋。

你太棒了,感谢你的帮助。 - Eqr444

6
为什么不使用栈来解决它?这样只需要O(n)的复杂度。
  1. Just parse the string and everytime you come across a '(', push it to the stack and everytime you come across a ')' , pop from the stack. else, put the character in a buffer.
  2. If the stack is empty while pushing a '(' then that means it is in a company name so also put that in the buffer.
  3. Similarly, if the stack isn't empty after popping, then put the ')' in the buffer as it is part of the company name.
  4. If the stack is empty after popping, that means that the first company name has ended and the buffer value is the name of the company and clear the buffer.

    String string = "(Motor (Sport) (racing) Ltd.) (Motorsport racing (Ltd.)) (Motorsport racing Ltd.)";
    List<String> result = new ArrayList();
    StringBuffer buffer = new StringBuffer();
    
    Stack<Character> stack = new Stack<Character>();
    for (int j = 0; j < string.length(); j++) {
        if (string.charAt(j) == '(') {
            if (!stack.empty())
                buffer.append('(');
            stack.push('(');
        } else if (string.charAt(j) == ')') {
            stack.pop();
            if (stack.empty()) {
                result.add(buffer.toString());
                buffer = new StringBuffer();
            }else
                buffer.append(')');
        }else{
            buffer.append(string.charAt(j));
        }
    }
    
    for(int i=0;i<result.size();i++){
        System.out.println(result.get(i));
    }
    

这应该是一条注释,旨在提出不同的方法建议。回答问题“I需要帮助使用A方法”时,用“使用B方法”并不能真正解决错误(请参阅neng_liu答案的注释)。 - ifloop
6
很酷的是,你解释了你的-1评分。然而,这种评论和评分使得SO变得不太友好。我认为发布超出常规范围的答案是可以的。有时候这些答案会很受欢迎,比如当OP想用正则表达式解析XML时。 - Tamas Rev
由于您只将 '(' 推入堆栈,因此您不需要真正的堆栈,只需要一个 int depth 来跟踪堆栈深度,即您拥有的未关闭括号的数量。 - Boann
@Boann 你说得对。当时我没有想到。 - Napstablook

4

我看到每个左括号都有一个对应的右括号,并且我不认为出现嵌套的括号是可能的。因此,具有平衡的括号但没有嵌套的括号会导致以下正则表达式:

\(((?:[^()]*|\([^)]*\))*)\)

你只需要访问第一个捕获组即可。
演示链接: 实时演示 分解如下:
  • \( 匹配开括号
    • ( 开始捕获组1
      • (?: 开始非捕获组1
        • [^()]* 匹配不在集合内的字符(可选)
        • |
        • \([^\)]*\) 匹配圆括号组
      • )* 多次匹配,结束非捕获组1
    • ) 结束捕获组1
  • \) 匹配闭括号

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接