Java: Как заменить последовательные символы одним символом? - PullRequest
0 голосов
/ 07 апреля 2020

Как заменить последовательные символы одним символом в java?

String fileContent = "def  mnop.UVW";
String oldDelimiters = " .";
String newDelimiter = "!";
for (int i = 0; i < oldDelimiters.length(); i++){
    Character character = oldDelimiters.charAt(i);
    fileContent = fileContent.replace(String.valueOf(character), newDelimiter);
}

Токовый выход: def!!mnop!UVW

Требуемый выход: def!mnop!UVW

Обратите внимание, что два пробела заменены двумя восклицательными знаками. Как заменить последовательные разделители одним разделителем?

Ответы [ 4 ]

2 голосов
/ 07 апреля 2020

Поскольку вы хотите сопоставлять последовательные символы из старого разделителя, решение с помощью регулярных выражений здесь не представляется возможным. Вместо этого вы можете сопоставить символ с символом, если он принадлежит одному из старых символов разделителя, а затем установить его с новым, как показано ниже.

import java.util.*;
public class Main{
    public static void main(String[] args) {
        String fileContent = "def  mnop.UVW";
        String oldDelimiters = " .";

        // add all old delimiters in a set for fast checks
        Set<Character> set = new HashSet<>();
        for(int i=0;i<oldDelimiters.length();++i) set.add(oldDelimiters.charAt(i));

        /* 
           match all consecutive chars at once, check if it belongs to an old delimiter 
           and replace it with the new one
        */

        String newDelimiter = "!";
        StringBuilder res = new StringBuilder("");
        for(int i=0;i<fileContent.length();++i){
            if(set.contains(fileContent.charAt(i))){
                while(i + 1 < fileContent.length() && fileContent.charAt(i) == fileContent.charAt(i+1)) i++;
                res.append(newDelimiter);
            }else{
                res.append(fileContent.charAt(i));        
            }
        }

        System.out.println(res.toString());
    }
}

Демо: https://onlinegdb.com/r1BC6qKP8

1 голос
/ 07 апреля 2020

Пока я не использовал 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 верны.

1 голос
/ 07 апреля 2020
s = s.replaceAll("([ \\.])[ \\.]+", "$1");

Или, если необходимо заменить только несколько одинаковых разделителей:

s = s.replaceAll("([ \\.])\\1+", "$1");
  • [....] - группа альтернативных символов
  • Первая (...) - группа 1, $1
  • \\1 - текст первой группы
1 голос
/ 07 апреля 2020

Самая большая сложность в использовании регулярного выражения для этого - создать выражение из вашей строки oldDelimiters. Например:

String oldDelimiters = " .";
String expression = "\\" + String.join("+|\\", oldDelimiters.split("")) + "+";
String text = "def  mnop.UVW;abc .df";
String result = text.replaceAll(expression, "!");

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

Где сгенерированное выражение выглядит как \ +|\.+, т. Е. Каждый символ определяется количественно и представляет собой одну альтернативу выражения. Двигатель будет соответствовать и заменять одну альтернативу за раз, если он может быть подобран. result теперь содержит:

def!mnop!UVW;abc!!df

Не уверен, насколько это обратно совместимо, из-за поведения split() в предыдущих версиях Java (создание начального пробела при разбиении на пустую строку), но с в текущих версиях это должно быть хорошо.

Редактировать: Как и раньше, это ломается, если символы-разделители содержат цифры или символы, представляющие неэкранированные токены регулярных выражений (то есть 1, b, et c .).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...