Java regex: заменить все символы на "+", кроме экземпляров заданной строки

У меня есть следующая проблема:

Заменить все символы в строке символом + кроме экземпляров данной строки в методе

например, если строка была abc123efg и они хотят, чтобы я заменил каждый символ, кроме каждого экземпляра 123 тогда он станет +++123+++.

Я полагал, что регулярное выражение, вероятно, является лучшим для этого, и я придумал это.

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

где str - переменная, но не позволяет мне использовать метод, не помещая его в цитаты. Если я просто хочу заменить переменную string str, как я могу это сделать? Я запускал его с строкой, введенной вручную, и работал над этим методом, но могу ли я просто ввести переменную?

на данный момент я считаю, что он ищет строку "str", а не переменную строку.

Вот выход его права для многих случаев, за исключением двух :(

enter image description here

Список открытых тестовых примеров:

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"

Ответ 1

Похоже, это plusOut проблема на CodingBat.

У меня было 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 для предотвращения интерпретации word как синтаксиса regex методом replaceAll.

Решение 3: Regex с \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 по умолчанию удаляет пустую строку в конце массива, нам нужно использовать -1 во втором параметре, чтобы split -1 пустые строки.
  • Затем мы создаем поток из массива и заменяем остальные как строки +. В Java 11 мы можем использовать s → String.repeat(s.length()).
  • Остальное просто преобразует Stream в Iterable (List в этом случае) и присоединяет их к результату

Ответ 2

Это немного сложнее, чем вы могли бы изначально подумать, потому что вам не просто нужно сопоставлять символы, но отсутствие конкретной фразы - отрицательный набор символов недостаточно. Если строка равна 123, вам понадобится:

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

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

То есть - lookbehind для начала строки или "123", убедитесь, что на текущую позицию не следует 123, затем lazy-repeat любого символа, пока lookahead не будет соответствовать "123" или концу строки. Это будет соответствовать всем символам, которые не находятся в подстроке "123". Затем, вам нужно заменить каждый символ с +, после чего вы можете использовать appendReplacement и StringBuffer для создания строки результата:

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 может содержать символ со специальным значением в регулярном выражении, вам сначала нужно сбежать от него, прежде чем конкатенация в шаблон.

Ответ 3

Вы можете сделать это в одной строке:

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

Это необязательно фиксирует "123" по обе стороны каждого символа и возвращает их (пустой, если нет "123"):

Ответ 4

Поэтому вместо того, чтобы придумывать регулярное выражение, которое соответствует отсутствию строки. Мы могли бы просто сопоставить выбранную фразу и добавить + количество пропущенных символов.

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('+');
}

Ответ 5

Абсолютно просто для удовольствия, решение с использованием 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();
}

Ответ 6

Для выполнения этой работы вам понадобится зверь шаблона. Скажем, вы, например, работаете на следующем тестовом примере:

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". Регулярные выражения, безусловно, не подходят для этого, но я думал, что это возможно. После того, как тыкаешься, я не уверен, что существует даже шаблон, который мог бы сделать эту работу.

Ответ 7

Проблема в вашем решении, в которую вы помещаете набор строк экземпляра str.replaceAll("[^str]","+") который он исключает любой символ из переменной str и это не решит вашу проблему

EX: при попытке str.replaceAll("[^XYZ]","+") он исключает любую комбинацию символов X, символа Y и символа Z из вашего метода замещения, чтобы вы получили " ++XY+++XYZ ".

На самом деле вы должны исключить последовательность символов вместо str.replaceAll.

Вы можете сделать это, используя группу захвата таких символов, как (XYZ) затем используйте отрицательный lookahead для соответствия строке, которая не содержит последовательности символов: ^((?!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 = "++".