Пока я не использовал regex, я подумал, что нужно решение с StreamS
, потому что все любят потоки:
private static class StatefulFilter implements Predicate<String> {
private final String needle;
private String last = null;
public StatefulFilter(String needle) {
this.needle = needle;
}
@Override
public boolean test(String value) {
boolean duplicate = last != null && last.equals(value) && value.equals(needle);
last = value;
return !duplicate;
}
}
public static void main(String[] args) {
System.out.println(
"def mnop.UVW"
.codePoints()
.sequential()
.mapToObj(c -> String.valueOf((char) c))
.filter(new StatefulFilter(" "))
.map(x -> x.equals(" ") ? "!" : x)
.collect(Collectors.joining(""))
);
}
Пример запуска: https://onlinegdb.com/BkY0R2twU
Объяснение:
Теоретически у вас не должно быть фильтра с сохранением состояния, но технически, пока поток не распараллелен, он работает нормально:
.codePoints()
- разбивает String
на Stream
.sequential()
- поскольку мы заботимся о порядке символов, наша Stream
может не обрабатываться параллельно
.mapToObj(c -> String.valueOf((char) c))
- сравнение в фильтре становится более интуитивно понятным, если мы конвертируем в String
, но в действительности это не нужно
.filter(new StatefulFilter(" "))
- здесь мы отфильтровываем все пробелы, следующие за другим пробелом
.map(x -> x.equals(" ") ? "!" : x)
- теперь мы можем заменить оставшиеся пробелы восклицательными знаками
.collect(Collectors.joining(""))
- и, наконец, мы можем объединить символы, чтобы воссоздать String
StatefulFilter
сама по себе довольно прямолинейна - она проверяет, есть ли у нас предыдущий символ вообще, b) является ли предыдущий символ тем же, что и текущий символ, и c) является ли текущий символ разделителем (пробелом). Возвращает false
(то есть символ удаляется), только если все a, b и c верны.