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 = "++"
.