Удаление пробелов, не находящихся в кавычках
Есть строка String str = "\"hi, pasha\" + \"hi \"";
Из нее надо сделать такую: "\"hi, pasha\" + \"hi \""
(то есть убрать все повторяющиеся подряд пробелы, которые не находятся между кавычек)
Я пытался сделать вот так:
public static String removeDuplicatedSpaces(String string) {
ArrayList<Character> chars = new ArrayList<>();
for (char c : string.toCharArray()) {
chars.add(c);
}
String removed = "";
boolean isIn = false;
for (int i = 0; i < chars.size(); i++) {
if (chars.get(i) == '"') {
isIn = !isIn;
}
if (chars.get(i) == ' ' && chars.get(i + 1) == ' ' && !isIn && i != chars.size() - 1) {
chars.remove(i + 1);
}
else {
removed += chars.get(i);
}
}
return removed;
}
Но выводит "hi, pasha" +"hi "
Ответы (1 шт):
Основная проблема в представленном коде для заданного примера, проверяющего только дублированные пробелы, сводится к тому, что после удаления второго пробела нужно "остаться" на предыдущей позиции в списке символов, то есть декрементировать i
.
Есть также баг в том, что сначала происходит обращение к следующему символу и лишь потом проверяется выход за пределы списка символов, что приводит к исключению IndexOutOfBoundsException
, когда несколько пробелов находятся в конце исходной строки.
Исправленный код выглядит так:
// ...
if (chars.get(i) == ' ' && !isIn && i < chars.size() - 1 && chars.get(i + 1) == ' ') {
System.out.println("CHAR : '" + chars.get(i) + "' - REMOVING");
chars.remove(i--); // удалить текущий пробел и сохранить позицию
}
// ...
Результат работы теста (без отладочного вывода):
System.out.println("[" + removeDuplicatedSpaces("\"hi, pasha\" + \"hi \", how are ya doin? ") + "]");
["hi, pasha" + "hi ", how are ya doin? ]
Однако следует заметить, что для решения данной задачи преобразование строки в список символов с последующим удалением символов из этого списка и конкатенация в цикле в строке removed += chars.get(i)
значительно снижают эффективность метода.
Кроме того, можно обрабатывать не только пробелы, но и пробельные символы (например, табуляцию \t
), используя метод Character::isWhitespace
.
Более производительный способ заключается в использовании StringBuilder
, для которого резервируется размер строки, и добавляются только нужные символы:
public static String removeDuplicatedSpaces2(String str) {
if (null == str || str.isEmpty()) return str;
int n = str.length();
StringBuilder sb = new StringBuilder(n);
boolean inQuotes = false;
boolean prevSpace = false;
for (int i = 0; i < n; i++) {
char c = str.charAt(i);
if (c == '"') {
inQuotes = !inQuotes;
} else if (Character.isWhitespace(c)) {
if (!inQuotes && prevSpace) {
continue;
}
prevSpace = true;
} else {
prevSpace = false;
}
sb.append(c);
}
return sb.toString();
}
Результат теста аналогичен предыдущему.
И наконец, для наиболее лаконичного решения можно составить регулярное выражение и воспользоваться методом из Java 9+ Matcher::replaceAll(Function<MatchResult, String> replacer)
:
private static Pattern SPACES = Pattern.compile("(\"[^\"]*(\"|$))|\\s+");
public static String removeDuplicatedSpacesRegex(String str) {
return SPACES.matcher(str).replaceAll(mr -> null == mr.group(1) ? " " : mr.group(1));
}
то есть, регулярка (\"[^\"]*(\"|$))|\\s
ищет либо текст в кавычках (\"[^\"]*(\"|$))
(причём кавычка может быть незакрытая) как первую группу, либо один и более пробельных символов \\s+
.
Затем лямбда-функция mr -> null == mr.group(1) ? " " : mr.group(1)
либо возвращает соответственно первую группу group(1)
как есть, либо заменяет последовательность пробельных символов одним пробелом.
Замечание: при необходимости вместо стандартного класса пробельных символов
\s : [ \t\n\x0B\f\r]
можно использовать класс горизонтальных пробельных символов
\h : [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
.