Замена нескольких подстрок в Java, когда замещающий текст перекрывает текст поиска - PullRequest
6 голосов
/ 23 сентября 2011

Скажем, у вас есть следующая строка:

cat dog fish dog fish cat

Вы хотите заменить все cats на dogs, все dogs на fish и все fish на cats,Интуитивно понятно, что ожидаемый результат:

dog fish cat fish cat dog

Если вы попробуете очевидное решение, перебрав с помощью replaceAll(), вы получите:

  1. (оригинал) cat dog fish dog fish cat
  2. (кошка -> собака) dog dog fish dog fish dog
  3. (собака -> рыба) fish fish fish fish fish fish
  4. (рыба -> кошка) cat cat cat cat cat cat

Понятно, что это не намеченный результат.Так каков самый простой способ сделать это?Я могу что-то сделать вместе с Pattern и Matcher (и большим количеством Pattern.quote() и Matcher.quoteReplacement()), но я отказываюсь верить, что я первый, кто столкнулся с этой проблемой, и нет библиотечной функции, чтобы решить ее,

(FWIW, фактический случай немного сложнее и не требует прямых свопов.)

Ответы [ 5 ]

8 голосов
/ 25 сентября 2011

Похоже, StringUtils.replaceEach в Apache Commons делает то, что вы хотите:

StringUtils.replaceEach("abcdeab", new String[]{"ab", "cd"}, new String[]{"cd", "ab"});
// returns "cdabecd"

Обратите внимание, что документ по вышеуказанным ссылкам, кажется, ошибочным.Подробности см. В комментариях ниже.

7 голосов
/ 23 сентября 2011
String rep = str.replace("cat","§1§").replace("dog","§2§")
                .replace("fish","§3§").replace("§1§","dog")
                .replace("§2§","fish").replace("§3§","cat");

Гадкий и неэффективный, как ад, но работает.


Хорошо, вот более сложная и общая версия.Я предпочитаю использовать регулярное выражение, а не сканер.Таким образом, я могу заменить произвольные строки, а не просто слова (что может быть лучше или хуже).В любом случае, здесь идет:

public static String replace(
    final String input, final Map<String, String> replacements) {

    if (input == null || "".equals(input) || replacements == null 
        || replacements.isEmpty()) {
        return input;
    }
    StringBuilder regexBuilder = new StringBuilder();
    Iterator<String> it = replacements.keySet().iterator();
    regexBuilder.append(Pattern.quote(it.next()));
    while (it.hasNext()) {
        regexBuilder.append('|').append(Pattern.quote(it.next()));
    }
    Matcher matcher = Pattern.compile(regexBuilder.toString()).matcher(input);
    StringBuffer out = new StringBuffer(input.length() + (input.length() / 10));
    while (matcher.find()) {
        matcher.appendReplacement(out, replacements.get(matcher.group()));
    }
    matcher.appendTail(out);
    return out.toString();
}

Тестовый код:

System.out.println(replace("cat dog fish dog fish cat",
    ImmutableMap.of("cat", "dog", "dog", "fish", "fish", "cat")));

Вывод:

собака рыба кот рыба кот собака собака

Очевидно, что это решение имеет смысл только для многих замен, в противном случае это огромный перебор.

4 голосов
/ 23 сентября 2011

Я бы создал StringBuilder и затем проанализировал бы текст один раз , по одному слову за раз, передавая неизмененные слова или измененные слова по мере продвижения. Я бы не стал разбирать его для каждого свопа, как вы предлагаете.

Так что вместо того, чтобы делать что-то вроде:

// pseudocode
text is new text swapping cat with dog
text is new text swapping dog with fish
text is new text swapping fish with cat

Я бы сделал

for each word in text
   if word is cat, swap with dog
   if word is dog, swap with fish
   if word is fish, swap with cat
   transfer new word (or unchanged word) into StringBuilder.

Я бы, вероятно, сделал для этого метод swap (...) и использовал бы HashMap для обмена.

Например

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class SwapWords {
   private static Map<String, String> myMap = new HashMap<String, String>();

   public static void main(String[] args) {
      // this would really be loaded using a file such as a text file or xml
      // or even a database:
      myMap.put("cat", "dog");
      myMap.put("dog", "fish");
      myMap.put("fish", "dog");

      String testString = "cat dog fish dog fish cat";

      StringBuilder sb = new StringBuilder();
      Scanner testScanner = new Scanner(testString);
      while (testScanner.hasNext()) {
         String text = testScanner.next();
         text = myMap.get(text) == null ? text : myMap.get(text);
         sb.append(text + " ");
      }

      System.out.println(sb.toString().trim());
   }
}
0 голосов
/ 23 июня 2018

Вот способ сделать это без регулярных выражений.

Я заметил, что каждый раз, когда часть строки a заменяется на b, b всегда будет частью конечной строки.Таким образом, вы можете игнорировать b из строки с этого момента.

Мало того, что после замены a на b там останется «пробел».Замена в том месте, где должно быть b, не может быть произведена.

Эти действия выглядят очень похоже на split.split увеличьте значения (сделав "пробел" между строками), выполните дальнейшие замены для каждой строки в массиве, затем соедините их обратно.

Например:

// Original
"cat dog fish dog fish cat"

// Replace cat with dog
{"", "dog fish dog fish", ""}.join("dog")

// Replace dog with fish
{
    "",
    {"", " fish ", " fish"}.join("fish")
    ""
}.join("dog")

// Replace fish with cat
{
    "",
    {
        "",
        {" ", " "}.join("cat"),
        {" ", ""}.join("cat")
    }.join("fish")
    ""
}.join("dog")

Пока что самый интуитивный способ (для меня) - сделать это рекурсивно:

public static String replaceWithJointMap(String s, Map<String, String> map) {
    // Base case
    if (map.size() == 0) {
        return s;
    }

    // Get some value in the map to replace
    Map.Entry pair = map.entrySet().iterator().next();
    String replaceFrom = (String) pair.getKey();
    String replaceTo = (String) pair.getValue();

    // Split the current string with the replaceFrom string
    // Use split with -1 so that trailing empty strings are included
    String[] splitString = s.split(Pattern.quote(replaceFrom), -1);

    // Apply replacements for each of the strings in the splitString
    HashMap<String, String> replacementsLeft = new HashMap<>(map);
    replacementsLeft.remove(replaceFrom);

    for (int i=0; i<splitString.length; i++) {
        splitString[i] = replaceWithJointMap(splitString[i], replacementsLeft);
    }

    // Join back with the current replacements
    return String.join(replaceTo, splitString);
}

Я не думаю, что это очень эффективно.

0 голосов
/ 30 ноября 2015
public class myreplase {
    public Map<String, String> replase;

    public myreplase() {
        replase = new HashMap<String, String>();

        replase.put("a", "Apple");
        replase.put("b", "Banana");
        replase.put("c", "Cantalope");
        replase.put("d", "Date");
        String word = "a b c d a b c d";

        String ss = "";
        Iterator<String> i = replase.keySet().iterator();
        while (i.hasNext()) {
            ss += i.next();
            if (i.hasNext()) {
                ss += "|";
            }
        }

        Pattern pattern = Pattern.compile(ss);
        StringBuilder buffer = new StringBuilder();
        for (int j = 0, k = 1; j < word.length(); j++,k++) {
            String s = word.substring(j, k);
            Matcher matcher = pattern.matcher(s);
            if (matcher.find()) {
                buffer.append(replase.get(s));
            } else {
                buffer.append(s);
            }
        }
        System.out.println(buffer.toString());
    }

    public static void main(String[] args) {
        new myreplase();
    }
}

Вывод: - Яблочная банановая канталопа Дата Яблочная банановая канталопа Дата

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