正则表达式去除所有方括号,但保留某个前缀后面的方括号

3
因此,我有一个字符串。大多数情况下,如果该字符串中有方括号,会发生一些错误。但是,在少数情况下,需要保留这些方括号。需要保留的这些方括号由特定前缀标识。例如,如果字符串为:
apple][s [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots [][[]]][]
我想要将其转换为:
apples pears prefix:[oranges] lemons persimmons peaches apricots
我已经找到了一个非常复杂的解决方案,它看起来像这样:
public class Debracketizer
{
    public static void main( String[] args )
    {
        String orig = "apples [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots";
        String result = debracketize(orig);
        System.out.println(result);
    }

    private static void debracketize( String orig )
    {
        String result1 = replaceAll(orig,
                                    Pattern.compile("\\["), 
                                    "",
                                    ".*prefix:$");

        String result2 = replaceAll(result1,
                                    Pattern.compile("\\]"),
                                    "",
                                    ".*prefix:\\[[^\\]]+$");

        System.out.println(result2);
    }

    private static String replaceAll( String orig, Pattern pattern, 
                                      String replacement, String skipPattern )
    {
        String quotedReplacement = Matcher.quoteReplacement(replacement);
        Matcher matcher = pattern.matcher(orig);
        StringBuffer sb = new StringBuffer();
        while( matcher.find() )
        {
            String resultSoFar = orig.substring(0, matcher.start());
            if (resultSoFar.matches(skipPattern)) {
                matcher.appendReplacement(sb, matcher.group());
            } else {
                matcher.appendReplacement(sb, quotedReplacement);
            }
        }
        matcher.appendTail(sb);
        return sb.toString();
    }
}

我相信一定有更好的方法来解决这个问题 - 最好只需要一个简单的正则表达式和一个简单的String.replaceAll()函数。但是我还没有想出来。
(我之前问过一个部分问题,但我无法看到如何适应完整情况的答案。这会教我提出部分问题的教训。)
6个回答

1
这个一行代码:
String resultString = subjectString.replaceAll("(?<!prefix:(?:\\[\\w{0,2000000})?)[\\[\\]]", "");

当应用于:apple][s [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots [][[]]][]

将会给你所需的结果:

apples pears prefix:[oranges] lemons persimmons peaches apricots 

你唯一的限制是前缀:[]之间的单词可以拥有的最大字符数。在这种情况下,限制为2000000。这个限制来自于Java,因为它不支持负回顾中的无限重复。


谢谢!我应该想到可以通过大的有限匹配来绕过回顾限制,但我没有。 - David Moles

1

不要走正则表达式的路,因为那条路会永远遮蔽你的视线。可以考虑以下方法或其变体。根据合理的分隔符(例如 "prefix["),拆分字符串并聪明地删除其他大括号。

这里是一个简单的算法(StringUtils 是 org.apache.commons.lang.StringUtils):

  1. 将字符串按"prefix["进行分割。对此,StringUtils.splitByWholeSeparator()似乎是个不错的选择(将返回值存储在blam中)。
  2. 从结果字符串中去掉所有的"["字符。可以使用StringUtils.stripAll(blam)来实现。
  3. 对于blam中的每个字符串,执行以下操作:
    1. 如果是第一个字符串,去掉所有的"]"字符。StringUtils.strip(blam[0], ']');将blam[0]替换为这个字符串。
    2. 如果不是第一个字符串,
    3. 使用分隔符"]"对字符串进行分割(将返回值存储在kapow中)。
    4. 根据kapow的每个元素构造一个字符串(命名为smacky)。在添加完第0个元素后,附加上']'。
    5. 用smacky替换blam[index]处的字符串。
  4. 通过将blam数组中的所有字符串连接起来构造最终结果。
  5. 跳起快乐的舞蹈。

1

有趣的问题。这里是一个经过测试的替代解决方案,它不使用回顾。

public class TEST
{
    public static void main( String[] args )
    {
        String orig = "apples [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots";
        String result = debracketize(orig);
        System.out.println(result);
    }

    private static String debracketize( String orig )
    {
        String re = // Don't indent to allow wide regex comments.
"(?x)                         # Set free-spacing mode.            \n" +
"# Either capture (and put back via replace) stuff to be kept...  \n" +
"  (                          # $1: Stuff to be kept.             \n" +
"    prefix:\\[[^\\[\\]]+\\]  # Either the special sequence,      \n" +
"  | (?:                      # or...                             \n" +
"      (?!                    # (Begin negative lookahead.)       \n" +
"        prefix:              # If this is NOT the start          \n" +
"        \\[[^\\[\\]]+\\]     # of the special sequence,          \n" +
"      )                      # (End negative lookahead.)         \n" +
"      [^\\[\\]]              # then match one non-bracket char.  \n" +
"    )+                       # Do this one char at a time.       \n" +
"  )                          # End $1: Stuff to be kept.         \n" +
"| # Or... Don't capture stuff to be removed (un-special brackets)\n" +
"  [\\[\\]]+                  # One or more non-special brackets.";
        return orig.replaceAll(re, "$1");
    }
}

这种方法使用两个全局替代方案。第一个替代方案捕获(然后替换)特殊序列和非括号字符,第二个替代方案匹配(并删除)非特殊括号。


我认为我会保留这个hack版本,因为它稍微容易让下一个程序员理解,但这真是太棒了——将问题转化并考虑要保留的部分,而不是要舍弃的部分,这从未在我脑海中出现过。谢谢。 - David Moles

0

如果您有一对不必担心在原始文本中出现的字符(例如<>),那么您可以先将要保留的方括号翻译成这些字符,然后删除其余部分,并将翻译后的方括号改回来。

以下是Ruby代码示例(希望转换到Java不会太难,您只需要进行全局搜索替换并使用捕获组):

>> s = 'apple][s [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots [][[]]][]'
=> "apple][s [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots [][[]]][]"
>> s.gsub(/([^\[\]]+):\[([^\[\]]+)\]/, '\1:<\2>').gsub(/[\[\]]/,'').gsub(/</,'[').gsub(/>/,']')
=> "apples pears prefix:[oranges] lemons persimmons peaches apricots "

是的,即使没有一个字符可以保证安全,我也可以使用类似于“THIS_IS_THE_OPENING_BRACKET”、“THIS_IS_THE_CLOSING_BRACKET”的东西。但是虽然这样更加简洁,但我不确定它是否更美观。 - David Moles

0

这是您的正则表达式解决方案:

input.replaceAll("((?<!prefix:)\\[(?!oranges)|(?<!prefix:\\[oranges)\\])", "");

它使用两个负向先行断言来防止删除保护前缀周围的方括号。如果您想要保护多个术语,可以通过在正则表达式中将oranges更改为(oranges|apples|pears)来实现。

这是一个使用您的数据进行的测试:

public static void main(String... args) throws InterruptedException {
     String input = "apple][s [pears] prefix:[oranges] lemons ]persimmons[ pea[ches ap]ricots [][[]]][]";
     String result = input.replaceAll("((?<!prefix:)\\[(?!oranges)|(?<!prefix:\\[oranges)\\])", "");
     System.out.println(result);
}

输出:

apples pears prefix:[oranges] lemons persimmons peaches apricots

这对于“橙子”有效,但如果我不知道括号内的内容,则无法正常工作 - 如果您尝试使用[^\\[]+]而不是oranges,则会出现“后顾组没有最大长度”的错误。 :( - David Moles

0

1 找出与 prefix:\[[^\]]+\] 匹配的结果

2 使用相同的正则表达式来分割字符串

3 对于每个数组元素,删除 ] 或 [ (您的示例有两个元素)

4 将步骤 1 中的结果与元素连接起来。


你有什么建议来寻找、存储和查找匹配的最佳方法吗? - David Moles

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