繁体   English   中英

Java正则表达式:除了给定字符串的实例外,用`+`替换所有字符

[英]Java regex: Replace all characters with `+` except instances of a given string

我有以下问题说明

使用+符号替换字符串中的所有字符,但方法中给定字符串的实例除外

所以例如,如果给出的字符串是abc123efg并且他们希望我替换除123每个实例之外的每个字符,那么它将变为+++123+++

我认为正则表达式可能是最好的,我想出了这个。

str.replaceAll("[^str]","+") 

其中str是一个变量,但它不允许我使用该方法而不将其放在引号中。 如果我只想替换变量字符串str我该怎么做? 我用字符串手动输入它运行它,它在方法上工作,但我可以输入一个变量吗?

截至目前我相信它正在寻找字符串“str”而不是变量字符串。

这是除了两个以外的很多情况的输出权:(

在此输入图像描述

开放测试用例列表:

plusOut("12xy34", "xy") → "++xy++"
plusOut("12xy34", "1") → "1+++++"
plusOut("12xy34xyabcxy", "xy") → "++xy++xy+++xy"
plusOut("abXYabcXYZ", "ab") → "ab++ab++++"
plusOut("abXYabcXYZ", "abc") → "++++abc+++"
plusOut("abXYabcXYZ", "XY") → "++XY+++XY+"
plusOut("abXYxyzXYZ", "XYZ") → "+++++++XYZ"
plusOut("--++ab", "++") → "++++++"
plusOut("aaxxxxbb", "xx") → "++xxxx++"
plusOut("123123", "3") → "++3++3"

看起来这是plusOut上的plusOut问题。

我有3个解决这个问题的方法,并为了好玩而写了一个新的流媒体解决方案。

解决方案1:循环和检查

从输入字符串中创建一个StringBuilder,并检查每个位置的单词。 如果不匹配则替换字符,如果找到则跳过单词的长度。

public String plusOut(String str, String word) {
  StringBuilder out = new StringBuilder(str);

  for (int i = 0; i < out.length(); ) {
    if (!str.startsWith(word, i))
      out.setCharAt(i++, '+');
    else
      i += word.length();
  }

  return out.toString();
}

这可能是初学者程序员的预期答案,尽管假设字符串不包含任何星体平面字符,它将由2个字符而不是1表示。

解决方案2:用标记替换单词,替换其余单词,然后恢复单词

public String plusOut(String str, String word) {
    return str.replaceAll(java.util.regex.Pattern.quote(word), "@").replaceAll("[^@]", "+").replaceAll("@", word);
}

不是一个合适的解决方案,因为它假定字符串中没有出现某个字符或字符序列。

请注意使用Pattern.quote来防止wordreplaceAll方法解释为正则表达式语法。

解决方案3:使用\\G表达式

public String plusOut(String str, String word) {
  word = java.util.regex.Pattern.quote(word);
  return str.replaceAll("\\G((?:" + word + ")*+).", "$1+");
}

构造正则表达式\\G((?:word)*+). ,它或多或少地做了解决方案1正在做的事情:

  • \\G确保匹配从上一场比赛的开始处开始
  • ((?:word)*+)选出0个或更多的word实例 - 如果有的话,这样我们就可以用$1替换它们。 这里的关键是占有量词*+ ,它强制正则表达式保留它找到的word任何实例。 否则,当word出现在字符串的末尾时,正则表达式将无法正常工作,因为正则表达式回溯匹配.
  • . 不会成为任何word一部分,因为前一部分已经连续出现word并且不允许回溯。 我们将用+替换它

解决方案4:流媒体

public String plusOut(String str, String word) {
  return String.join(word, 
    Arrays.stream(str.split(java.util.regex.Pattern.quote(word), -1))
      .map((String s) -> s.replaceAll("(?s:.)", "+"))
      .collect(Collectors.toList()));
}

我们的想法是通过拆分串word ,做剩下的更换,和他们一起回来word使用String.join方法。

  • 与上面相同,我们需要Pattern.quote来避免split word解释为正则表达式。 由于默认情况下split会删除数组末尾的空字符串,因此我们需要在第二个参数中使用-1split这些空字符串。
  • 然后我们从数组中创建一个流,并将其余部分替换为+字符串。 在Java 11中,我们可以使用s -> String.repeat(s.length())代替。
  • 其余的只是将Stream转换为Iterable(在本例中为List)并将它们连接起来以获得结果

这比你最初想象的要复杂一点,因为你不仅需要匹配字符 ,而且缺少特定的短语 - 否定的字符集是不够的。 如果字符串是123,您将需要:

(?<=^|123)(?!123).*?(?=123|$)

https://regex101.com/r/EZWMqM/1/

即 - 查看字符串开头或“123”,确保当前位置后面没有123,然后懒惰重复任何字符,直到前瞻符合“123”或字符串结尾。 这将匹配不在“123”子字符串中的所有字符。 然后,您需要用+替换每个字符,之后您可以使用appendReplacementStringBuffer来创建结果字符串:

String inputPhrase = "123";
String inputStr = "abc123efg123123hij";
StringBuffer resultString = new StringBuffer();
Pattern regex = Pattern.compile("(?<=^|" + inputPhrase + ")(?!" + inputPhrase + ").*?(?=" + inputPhrase + "|$)");
Matcher m = regex.matcher(inputStr);
while (m.find()) {
    String replacement = m.group(0).replaceAll(".", "+");
    m.appendReplacement(resultString, replacement);
}
m.appendTail(resultString);
System.out.println(resultString.toString());

输出:

+++123+++123123+++

请注意,如果inputPhrase可以包含正则表达式中具有特殊含义的字符,则必须在连接到模式之前先将它们转义。

你可以在一行中完成:

input = input.replaceAll("((?:" + str + ")+)?(?!" + str + ").((?:" + str + ")+)?", "$1+$2");

这可选地捕获每个字符两侧的“123”并将它们放回(如果没有“123”则为空白):

因此,而不是提出一个匹配缺少字符串的正则表达式。 我们也可以只匹配所选短语并附加+跳过的字符数。

StringBuilder sb = new StringBuilder();
Matcher m = Pattern.compile(Pattern.quote(str)).matcher(input);
while (m.find()) {
    for (int i = 0; i < m.start(); i++) sb.append('+');
    sb.append(str);
}
int remaining = input.length() - sb.length();
for (int i = 0; i < remaining; i++) {
    sb.append('+');
}

绝对只是为了它的乐趣,使用CharBuffer的解决方案(出乎意料的是它花了我原先希望的更多):

private static String plusOutCharBuffer(String input, String match) {
    int size = match.length();
    CharBuffer cb = CharBuffer.wrap(input.toCharArray());
    CharBuffer word = CharBuffer.wrap(match);

    int x = 0;
    for (; cb.remaining() > 0;) {
        if (!cb.subSequence(0, size < cb.remaining() ? size : cb.remaining()).equals(word)) {
            cb.put(x, '+');
            cb.clear().position(++x);
        } else {
            cb.clear().position(x = x + size);
        }
    }

    return cb.clear().toString();
}

要完成这项工作,你需要一个模式的野兽。 假设您正在使用以下测试用例作为示例:

plusOut("abXYxyzXYZ", "XYZ") → "+++++++XYZ"

你需要做的是在你的模式中构建一系列子句,以便一次匹配一个字符:

  • 任何不是“X”,“Y”或“Z”的字符 - [^XYZ]
  • 任何“X”后面跟着“YZ” - X(?!YZ)
  • 任何“Y”前面都没有“X” - (?<!X)Y
  • 任何“Y”后面跟着“Z” - Y(?!Z)
  • 任何“Z”前面没有“XY” - (?<!XY)Z

可以在此处找到此替换的示例: https//regex101.com/r/jK5wU3/4

下面是一个如何工作的例子(大多数情况下没有优化,但它有效):

import java.util.regex.Pattern;

public class Test {

    public static void plusOut(String text, String exclude) {

        StringBuilder pattern = new StringBuilder("");
        for (int i=0; i<exclude.length(); i++) {

            Character target    = exclude.charAt(i);
            String prefix       = (i > 0) ? exclude.substring(0, i) : "";
            String postfix      = (i < exclude.length() - 1) ? exclude.substring(i+1) : "";

            // add the look-behind (?<!X)Y
            if (!prefix.isEmpty()) {
                pattern.append("(?<!").append(Pattern.quote(prefix)).append(")")
                        .append(Pattern.quote(target.toString())).append("|");
            }

            // add the look-ahead X(?!YZ)
            if (!postfix.isEmpty()) {
                pattern.append(Pattern.quote(target.toString()))
                        .append("(?!").append(Pattern.quote(postfix)).append(")|");
            }

        }

        // add in the other character exclusion
        pattern.append("[^" + Pattern.quote(exclude) + "]");

        System.out.println(text.replaceAll(pattern.toString(), "+"));

    }

    public static void main(String  [] args) {

        plusOut("12xy34", "xy");
        plusOut("12xy34", "1");
        plusOut("12xy34xyabcxy", "xy");
        plusOut("abXYabcXYZ", "ab");
        plusOut("abXYabcXYZ", "abc");
        plusOut("abXYabcXYZ", "XY");
        plusOut("abXYxyzXYZ", "XYZ");
        plusOut("--++ab", "++");
        plusOut("aaxxxxbb", "xx");
        plusOut("123123", "3");

    }

}

更新:即使这不起作用,因为它不能处理只是重复字符的排除,如“xx”。 正则表达式绝对不是正确的工具,但我认为它可能是正确的。 在探索之后,我不太确定甚至存在可能使这项工作的模式。

您的解决方案中存在一组实例字符串str.replaceAll("[^str]","+") ,它将从变量str排除任何字符并且无法解决您的问题

EX :当你尝试str.replaceAll("[^XYZ]","+")它会从你的替换方法中排除字符X ,字符Y和字符Z任意组合,这样你就会得到“ ++XY+++XYZ ”。

实际上你应该在str.replaceAll排除一系列字符。

您可以使用捕获字符 (如(XYZ)然后使用负向前导来匹配不包含字符序列的字符串: ^((?!XYZ).)*$

检查此解决方案以获取有关此问题的更多信息,但您应该知道找到正则表达式直接执行此操作可能很复杂。

我找到了两个解决这个问题的简单方法:

解决方案1

除了给定字符串的实例外,您可以实现一个用' + '替换所有字符的方法:

String exWord = "XYZ";
String str = "abXYxyzXYZ";

for(int i = 0; i < str.length(); i++){
    // exclude any instance string of exWord from replacing process in str
    if(str.substring(i, str.length()).indexOf(exWord) + i == i){
        i = i + exWord.length()-1;
    }
    else{
        str = str.substring(0,i) + "+" + str.substring(i+1);//replace each character with '+' symbol
    }
}             

注意str.substring(i, str.length()).indexOf(exWord) + i这个if语句将排除任何exWord实例字符串替换str进程。

输出

+++++++XYZ

解决方案2

您可以使用ReplaceAll方法尝试此方法,它不需要任何复杂的正则表达式:

String exWord = "XYZ";
String str = "abXYxyzXYZ";

str = str.replaceAll(exWord,"*"); // replace instance string with * symbol
str = str.replaceAll("[^*]","+"); // replace all characters with + symbol except * 
str = str.replaceAll("\\*",exWord); // replace * symbol with instance string

注意 :仅当输入字符串str不包含任何*符号时,此解决方案才有效。

此外,您应该在短语实例字符串exWord中的正则表达式中转义具有特殊含义的任何字符,例如: exWord = "++"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM