Как заменить две строки так, чтобы они не заменяли другую?

Скажем, что у меня есть следующий код:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("foo", word1);
story = story.replace("bar", word2);

После выполнения этого кода значение story будет "Once upon a time, there was a foo and a foo."

Аналогичная проблема возникает, если я заменил их в обратном порядке:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("bar", word2);
story = story.replace("foo", word1);

Значение story будет "Once upon a time, there was a bar and a bar."

Моя цель - превратить story в "Once upon a time, there was a bar and a foo." Как я могу это сделать?

Ответ 1

Это непростая задача. И чем больше у вас параметров поиска, тем сложнее. У вас есть несколько вариантов, разбросанных по палитре уродливого, элегантного, эффективного расточительного:

  • Используйте StringUtils.replaceEach из Apache Commons в качестве @AlanHay. Это хороший вариант, если вы можете добавлять новые зависимости в свой проект. Вам может повезти: зависимость может быть включена уже в ваш проект

  • Используйте временный заполнитель в качестве @Jeroen и выполните замену в 2 этапа:

    • Замените все шаблоны поиска уникальным тегом, который не существует в исходном тексте
    • Замените заполнители реальной заменой цели

    Это не отличный подход по нескольким причинам: он должен гарантировать, что теги, используемые на первом шаге, действительно уникальны; он выполняет больше операций замены строк, чем действительно необходимо

  • Создайте регулярное выражение из всех шаблонов и используйте метод Matcher и StringBuffer, как предложено @arshajii. Это не страшно, но не так уж и важно, поскольку создание регулярного выражения является хакерским, и оно включает в себя StringBuffer, который вышел из моды некоторое время назад в пользу StringBuilder.

  • Используйте рекурсивное решение, предложенное @mjolka, разделяя строку на сопоставленные шаблоны и рекурсивное на остальных сегментах. Это прекрасное решение, компактное и довольно элегантное. Его слабость - потенциально много операций подстроки и конкатенации, а также ограничения размера стека, которые применяются ко всем рекурсивным решениям.

  • Разделите текст на слова и используйте потоки Java 8, чтобы элегантно выполнять замены в качестве @msandiford, но, конечно, это работает только в том случае, если вы в порядке с расщеплением на границах слов, что делает его непригодным в качестве общего решения

Здесь моя версия, основанная на идеях, заимствованных из Apache-реализация. Это не простой и элегантный, но он работает и должен быть относительно эффективным, без лишних шагов. В двух словах он работает следующим образом: повторно найдите следующий соответствующий шаблон поиска в тексте и используйте StringBuilder для накопления непревзойденных сегментов и замен.

public static String replaceEach(String text, String[] searchList, String[] replacementList) {
    // TODO: throw new IllegalArgumentException() if any param doesn't make sense
    //validateParams(text, searchList, replacementList);

    SearchTracker tracker = new SearchTracker(text, searchList, replacementList);
    if (!tracker.hasNextMatch(0)) {
        return text;
    }

    StringBuilder buf = new StringBuilder(text.length() * 2);
    int start = 0;

    do {
        SearchTracker.MatchInfo matchInfo = tracker.matchInfo;
        int textIndex = matchInfo.textIndex;
        String pattern = matchInfo.pattern;
        String replacement = matchInfo.replacement;

        buf.append(text.substring(start, textIndex));
        buf.append(replacement);

        start = textIndex + pattern.length();
    } while (tracker.hasNextMatch(start));

    return buf.append(text.substring(start)).toString();
}

private static class SearchTracker {

    private final String text;

    private final Map<String, String> patternToReplacement = new HashMap<>();
    private final Set<String> pendingPatterns = new HashSet<>();

    private MatchInfo matchInfo = null;

    private static class MatchInfo {
        private final String pattern;
        private final String replacement;
        private final int textIndex;

        private MatchInfo(String pattern, String replacement, int textIndex) {
            this.pattern = pattern;
            this.replacement = replacement;
            this.textIndex = textIndex;
        }
    }

    private SearchTracker(String text, String[] searchList, String[] replacementList) {
        this.text = text;
        for (int i = 0; i < searchList.length; ++i) {
            String pattern = searchList[i];
            patternToReplacement.put(pattern, replacementList[i]);
            pendingPatterns.add(pattern);
        }
    }

    boolean hasNextMatch(int start) {
        int textIndex = -1;
        String nextPattern = null;

        for (String pattern : new ArrayList<>(pendingPatterns)) {
            int matchIndex = text.indexOf(pattern, start);
            if (matchIndex == -1) {
                pendingPatterns.remove(pattern);
            } else {
                if (textIndex == -1 || matchIndex < textIndex) {
                    textIndex = matchIndex;
                    nextPattern = pattern;
                }
            }
        }

        if (nextPattern != null) {
            matchInfo = new MatchInfo(nextPattern, patternToReplacement.get(nextPattern), textIndex);
            return true;
        }
        return false;
    }
}

Единичные тесты:

@Test
public void testSingleExact() {
    assertEquals("bar", StringUtils.replaceEach("foo", new String[]{"foo"}, new String[]{"bar"}));
}

@Test
public void testReplaceTwice() {
    assertEquals("barbar", StringUtils.replaceEach("foofoo", new String[]{"foo"}, new String[]{"bar"}));
}

@Test
public void testReplaceTwoPatterns() {
    assertEquals("barbaz", StringUtils.replaceEach("foobar",
            new String[]{"foo", "bar"},
            new String[]{"bar", "baz"}));
}

@Test
public void testReplaceNone() {
    assertEquals("foofoo", StringUtils.replaceEach("foofoo", new String[]{"x"}, new String[]{"bar"}));
}

@Test
public void testStory() {
    assertEquals("Once upon a foo, there was a bar and a baz, and another bar and a cat.",
            StringUtils.replaceEach("Once upon a baz, there was a foo and a bar, and another foo and a cat.",
                    new String[]{"foo", "bar", "baz"},
                    new String[]{"bar", "baz", "foo"})
    );
}

Ответ 2

Вы используете промежуточное значение (которое еще не присутствует в предложении).

story = story.replace("foo", "lala");
story = story.replace("bar", "foo");
story = story.replace("lala", "bar");

В ответ на критику: если вы используете достаточно большую необычную строку, такую ​​как zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddjhodfqsqc, nvùq ^ μù; d & € sdq: d:;) àçàçlala и использовать это, вряд ли до такой степени, даже не обсудит, что пользователь когда-либо будет вводить это. Единственный способ узнать, есть ли пользователь, - это знать исходный код, и в этот момент вы столкнулись со всем другим уровнем забот.

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

Также подтверждая отличную рекомендацию @David Conrad в комментариях:

Не используйте какую-нибудь строку умным (тупо), выбранным, чтобы быть маловероятным. Используйте символы из области индивидуального использования Unicode, U + E000..U + F8FF. Сначала удалите все такие символы, поскольку они не должны законно находиться на входе (они имеют только специфическое для приложения значение в каком-либо приложении), а затем используют их как заполнители при замене.

Ответ 4

Вы можете попробовать что-то вроде этого, используя Matcher#appendReplacement и Matcher#appendTail:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar.";

Pattern p = Pattern.compile("foo|bar");
Matcher m = p.matcher(story);
StringBuffer sb = new StringBuffer();
while (m.find()) {
    /* do the swap... */
    switch (m.group()) {
    case "foo":
        m.appendReplacement(sb, word1);
        break;
    case "bar":
        m.appendReplacement(sb, word2);
        break;
    default:
        /* error */
        break;
    }
}
m.appendTail(sb);

System.out.println(sb.toString());
Once upon a time, there was a bar and a foo.

Ответ 5

Найдите первое слово для замены. Если он в строке, рекурсия на части строки перед вступлением и со стороны строки после появления.

В противном случае перейдите к следующему слову, которое нужно заменить.

Наивная реализация может выглядеть так:

public static String replaceAll(String input, String[] search, String[] replace) {
  return replaceAll(input, search, replace, 0);
}

private static String replaceAll(String input, String[] search, String[] replace, int i) {
  if (i == search.length) {
    return input;
  }
  int j = input.indexOf(search[i]);
  if (j == -1) {
    return replaceAll(input, search, replace, i + 1);
  }
  return replaceAll(input.substring(0, j), search, replace, i + 1) +
         replace[i] +
         replaceAll(input.substring(j + search[i].length()), search, replace, i);
}

Использование образца:

String input = "Once upon a baz, there was a foo and a bar.";
String[] search = new String[] { "foo", "bar", "baz" };
String[] replace = new String[] { "bar", "baz", "foo" };
System.out.println(replaceAll(input, search, replace));

Вывод:

Once upon a foo, there was a bar and a baz.

Менее наивная версия:

public static String replaceAll(String input, String[] search, String[] replace) {
  StringBuilder sb = new StringBuilder();
  replaceAll(sb, input, 0, input.length(), search, replace, 0);
  return sb.toString();
}

private static void replaceAll(StringBuilder sb, String input, int start, int end, String[] search, String[] replace, int i) {
  while (i < search.length && start < end) {
    int j = indexOf(input, search[i], start, end);
    if (j == -1) {
      i++;
    } else {
      replaceAll(sb, input, start, j, search, replace, i + 1);
      sb.append(replace[i]);
      start = j + search[i].length();
    }
  }
  sb.append(input, start, end);
}

К сожалению, Java String не имеет метода indexOf(String str, int fromIndex, int toIndex). Я опустил реализацию indexOf здесь, так как я не уверен, что это правильно, но его можно найти на ideone, вдоль с некоторыми грубыми таймингами различных решений, размещенных здесь.

Ответ 6

Однострочный в Java 8:

    story = Pattern
        .compile(String.format("(?<=%1$s)|(?=%1$s)", "foo|bar"))
        .splitAsStream(story)
        .map(w -> ImmutableMap.of("bar", "foo", "foo", "bar").getOrDefault(w, w))
        .collect(Collectors.joining());
  • Регулярные выражения Lookaround (?<=, ?=): http://www.regular-expressions.info/lookaround.html
  • Если слова могут содержать специальные регулярные выражения, используйте Pattern.quote для избегайте их.
  • Я использую guava ImmutableMap для краткости, но, очевидно, любая другая Карта также выполнит эту работу.

Ответ 7

Вот возможность потоков Java 8, которые могут быть интересны для некоторых:

String word1 = "bar";
String word2 = "foo";

String story = "Once upon a time, there was a foo and a bar.";

// Map is from untranslated word to translated word
Map<String, String> wordMap = new HashMap<>();
wordMap.put(word1, word2);
wordMap.put(word2, word1);

// Split on word boundaries so we retain whitespace.
String translated = Arrays.stream(story.split("\\b"))
    .map(w -> wordMap.getOrDefault(w,  w))
    .collect(Collectors.joining());

System.out.println(translated);

Вот пример того же алгоритма в Java 7:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar.";

// Map is from untranslated word to translated word
Map<String, String> wordMap = new HashMap<>();
wordMap.put(word1, word2);
wordMap.put(word2, word1);

// Split on word boundaries so we retain whitespace.
StringBuilder translated = new StringBuilder();
for (String w : story.split("\\b"))
{
  String tw = wordMap.get(w);
  translated.append(tw != null ? tw : w);
}

System.out.println(translated);

Ответ 8

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

  • Разделите историю на пробеле.
  • Замените каждый элемент, если foo заменит его на bar и vice varsa
  • Присоедините массив к одной строке

Если расщепление в пространстве неприемлемо, можно следовать этому альтернативному алгоритму. Сначала вам нужно использовать более длинную строку. Если строки являются foo и fool, вам нужно сначала использовать fool, а затем foo.

  • Разделить слово foo
  • Заменить панель с помощью foo для каждого элемента массива
  • Присоединиться к этому массиву после добавления строки после каждого элемента, кроме последнего

Ответ 9

Здесь менее сложный ответ с использованием карты.

private static String replaceEach(String str,Map<String, String> map) {

         Object[] keys = map.keySet().toArray();
         for(int x = 0 ; x < keys.length ; x ++ ) {
             str = str.replace((String) keys[x],"%"+x);
         }

         for(int x = 0 ; x < keys.length ; x ++) {
             str = str.replace("%"+x,map.get(keys[x]));
         }
         return str;
     }

И метод называется

Map<String, String> replaceStr = new HashMap<>();
replaceStr.put("Raffy","awesome");
replaceStr.put("awesome","Raffy");
String replaced = replaceEach("Raffy is awesome, awesome awesome is Raffy Raffy", replaceStr);

Выход: Удивительный Раффи, Раффи Раффи - потрясающий удивительный

Ответ 10

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

String regex = word1 + "|" + word2;
String[] values = Pattern.compile(regex).split(story);

String result;
foreach subStr in values
{
   subStr = subStr.replace(word1, word2);
   subStr = subStr.replace(word2, word1);
   result += subStr;
}

Ответ 11

Переключение только одного события

Если на входе имеется только одно вхождение каждой из подстановочных строк, вы можете сделать следующее:

Прежде чем перейти к любой замене, получите индексы вхождений слов. После этого мы заменяем только слово, найденное в этих индексах, и не все вхождения. Это решение использует StringBuilder и не создает промежуточный String как String.replace().

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

public static String swap(String src, String s1, String s2) {
    StringBuilder sb = new StringBuilder(src);
    int i1 = src.indexOf(s1);
    int i2 = src.indexOf(s2);

    sb.replace(i1, i1 + s1.length(), s2); // Replace s1 with s2
    // If s1 was before s2, idx2 might have changed after the replace
    if (i1 < i2)
        i2 += s2.length() - s1.length();
    sb.replace(i2, i2 + s2.length(), s1); // Replace s2 with s1

    return sb.toString();
}

Переключение произвольного числа событий

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

public static List<Integer> occurrences(String src, String s) {
    List<Integer> list = new ArrayList<>();
    for (int idx = 0;;)
        if ((idx = src.indexOf(s, idx)) >= 0) {
            list.add(idx);
            idx += s.length();
        } else
            return list;
}

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

public static String swapAll(String src, String s1, String s2) {
    List<Integer> l1 = occurrences(src, s1), l2 = occurrences(src, s2);

    StringBuilder sb = new StringBuilder(src);

    // Replace occurrences by decreasing index, alternating between s1 and s2
    for (int i1 = l1.size() - 1, i2 = l2.size() - 1; i1 >= 0 || i2 >= 0;) {
        int idx1 = i1 < 0 ? -1 : l1.get(i1);
        int idx2 = i2 < 0 ? -1 : l2.get(i2);
        if (idx1 > idx2) { // Replace s1 with s2
            sb.replace(idx1, idx1 + s1.length(), s2);
            i1--;
        } else { // Replace s2 with s1
            sb.replace(idx2, idx2 + s2.length(), s1);
            i2--;
        }
    }

    return sb.toString();
}

Ответ 12

Вы можете выполнить свою задачу с помощью следующего кода:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, in a foo, there was a foo and a bar.";
story = String.format(story.replace(word1, "%1$s").replace(word2, "%2$s"),
    word2, word1);

Он заменяет слова независимо от порядка. Вы можете распространить этот принцип на полезный метод, например:

private static String replace(String source, String[] targets, String[] replacements) throws IllegalArgumentException {
    if (source == null) {
        throw new IllegalArgumentException("The parameter \"source\" cannot be null.");
    }

    if (targets == null || replacements == null) {
        throw new IllegalArgumentException("Neither parameters \"targets\" or \"replacements\" can be null.");
    }

    if (targets.length == 0 || targets.length != replacements.length) {
        throw new IllegalArgumentException("The parameters \"targets\" and \"replacements\" must have at least one item and have the same length.");
    }

    String outputMask = source;
    for (int i = 0; i < targets.length; i++) {
        outputMask = outputMask.replace(targets[i], "%" + (i + 1) + "$s");
    }

    return String.format(outputMask, (Object[])replacements);
}

Что будет потребляться как:

String story = "Once upon a time, in a foo, there was a foo and a bar.";
story = replace(story, new String[] { "bar", "foo" },
    new String[] { "foo", "bar" }));

Ответ 13

Это работает и прост:

public String replaceBoth(String text, String token1, String token2) {            
    return text.replace(token1, "\ufdd0").replace(token2, token1).replace("\ufdd0", token2);
    }

Вы используете его следующим образом:

replaceBoth("Once upon a time, there was a foo and a bar.", "foo", "bar");

Примечание: это относится к строкам, не содержащим символ \ufdd0, который является символом постоянно, зарезервированным для внутреннего использования Unicode (см. http://www.unicode.org/faq/private_use.html):

Я не думаю, что это необходимо, но если вы хотите быть абсолютно безопасным, вы можете использовать:

public String replaceBoth(String text, String token1, String token2) {
    if (text.contains("\ufdd0") || token1.contains("\ufdd0") || token2.contains("\ufdd0")) throw new IllegalArgumentException("Invalid character.");
    return text.replace(token1, "\ufdd0").replace(token2, token1).replace("\ufdd0", token2);
    }

Ответ 14

Легко написать метод для этого, используя String.regionMatches:

public static String simultaneousReplace(String subject, String... pairs) {
    if (pairs.length % 2 != 0) throw new IllegalArgumentException(
        "Strings to find and replace are not paired.");
    StringBuilder sb = new StringBuilder();
    outer:
    for (int i = 0; i < subject.length(); i++) {
        for (int j = 0; j < pairs.length; j += 2) {
            String find = pairs[j];
            if (subject.regionMatches(i, find, 0, find.length())) {
                sb.append(pairs[j + 1]);
                i += find.length() - 1;
                continue outer;
            }
        }
        sb.append(subject.charAt(i));
    }
    return sb.toString();
}

Тестирование:

String s = "There are three cats and two dogs.";
s = simultaneousReplace(s,
    "cats", "dogs",
    "dogs", "budgies");
System.out.println(s);

Вывод:

Есть три собаки и два друга.

Это не сразу очевидно, но такая функция может по-прежнему зависеть от порядка, в котором указаны замены. Рассмотрим:

String truth = "Java is to JavaScript";
truth += " as " + simultaneousReplace(truth,
    "JavaScript", "Hamster",
    "Java", "Ham");
System.out.println(truth);

Вывод:

Java - это JavaScript, поскольку Ham - Hamster

Но отмените замену:

truth += " as " + simultaneousReplace(truth,
    "Java", "Ham",
    "JavaScript", "Hamster");

Вывод:

Java относится к JavaScript, поскольку Ham - для HamScript

Oops!:)

Поэтому иногда полезно всегда искать самое длинное совпадение (например, функция PHP strtr). Эта версия метода сделает это:

public static String simultaneousReplace(String subject, String... pairs) {
    if (pairs.length % 2 != 0) throw new IllegalArgumentException(
        "Strings to find and replace are not paired.");
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < subject.length(); i++) {
        int longestMatchIndex = -1;
        int longestMatchLength = -1;
        for (int j = 0; j < pairs.length; j += 2) {
            String find = pairs[j];
            if (subject.regionMatches(i, find, 0, find.length())) {
                if (find.length() > longestMatchLength) {
                    longestMatchIndex = j;
                    longestMatchLength = find.length();
                }
            }
        }
        if (longestMatchIndex >= 0) {
            sb.append(pairs[longestMatchIndex + 1]);
            i += longestMatchLength - 1;
        } else {
            sb.append(subject.charAt(i));
        }
    }
    return sb.toString();
}

Обратите внимание, что указанные выше методы чувствительны к регистру. Если вам нужна нечувствительная к регистру версия, это легко изменить, потому что String.regionMatches может принимать параметр ignoreCase.

Ответ 15

Если вы не хотите никаких зависимостей, вы можете просто использовать массив, который позволяет только одноразовое изменение. Это не самое эффективное решение, но оно должно работать.

public String replace(String sentence, String[]... replace){
    String[] words = sentence.split("\\s+");
    int[] lock = new int[words.length];
    StringBuilder out = new StringBuilder();

    for (int i = 0; i < words.length; i++) {
        for(String[] r : replace){
            if(words[i].contains(r[0]) && lock[i] == 0){
                words[i] = words[i].replace(r[0], r[1]);
                lock[i] = 1;
            }
        }

        out.append((i < (words.length - 1) ? words[i] + " " : words[i]));
    }

    return out.toString();
}

Затем он работает.

String story = "Once upon a time, there was a foo and a bar.";

String[] a = {"foo", "bar"};
String[] b = {"bar", "foo"};
String[] c = {"there", "Pocahontas"};
story = replace(story, a, b, c);

System.out.println(story); // Once upon a time, Pocahontas was a bar and a foo.

Ответ 16

Вы выполняете несколько операций поиска и замены на входе. Это приведет к нежелательным результатам, когда строки замены содержат строки поиска. Рассмотрим пример foo- > bar, bar-foo, вот результаты для каждой итерации:

  • Когда-то было фью и бар. (Вход)
  • Когда-то был бар и бар. (Foo- > бар)
  • Когда-то было foo и foo. (bar- > foo, output)

Вам нужно выполнить замену на одной итерации, не возвращаясь. Решение грубой силы выглядит следующим образом:

  • Искать входные данные из текущей позиции для завершения нескольких строк поиска до тех пор, пока не будет найдено совпадение
  • Замените соответствующую строку поиска соответствующей строкой замены
  • Установить текущую позицию следующего символа после замененной строки
  • Повторите

Функция, такая как String.indexOfAny(String[]) -> int[]{index, whichString}, была бы полезна. Вот пример (не самый эффективный):

private static String replaceEach(String str, String[] searchWords, String[] replaceWords) {
    String ret = "";
    while (str.length() > 0) {
        int i;
        for (i = 0; i < searchWords.length; i++) {
            String search = searchWords[i];
            String replace = replaceWords[i];
            if (str.startsWith(search)) {
                ret += replace;
                str = str.substring(search.length());
                break;
            }
        }
        if (i == searchWords.length) {
            ret += str.substring(0, 1);
            str = str.substring(1);
        }
    }
    return ret;
}

Некоторые тесты:

System.out.println(replaceEach(
    "Once upon a time, there was a foo and a bar.",
    new String[]{"foo", "bar"},
    new String[]{"bar", "foo"}
));
// Once upon a time, there was a bar and a foo.

System.out.println(replaceEach(
    "a p",
    new String[]{"a", "p"},
    new String[]{"apple", "pear"}
));
// apple pear

System.out.println(replaceEach(
    "ABCDE",
    new String[]{"A", "B", "C", "D", "E"},
    new String[]{"B", "C", "E", "E", "F"}
));
// BCEEF

System.out.println(replaceEach(
    "ABCDEF",
    new String[]{"ABCDEF", "ABC", "DEF"},
    new String[]{"XXXXXX", "YYY", "ZZZ"}
));
// XXXXXX
// note the order of search strings, longer strings should be placed first 
// in order to make the replacement greedy

Демо на IDEONE
Демо на IDEONE, альтернативный код

Ответ 17

Вы всегда можете заменить его словами, которые, как вы уверены, нигде больше не появятся в строке, а затем выполните вторую замену позже:

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("foo", "StringYouAreSureWillNeverOccur").replace("bar", "word2").replace("StringYouAreSureWillNeverOccur", "word1");

Обратите внимание, что это не будет работать, если "StringYouAreSureWillNeverOccur" действительно произойдет.

Ответ 18

Используя найденный ответ здесь, вы можете найти все вхождения строк, которые вы хотите заменить.

Итак, например, вы запускаете код в приведенном выше ответе SO. Создайте две таблицы индексов (скажем, bar и foo не отображаются только один раз в вашей строке), и вы можете работать с этими таблицами при замене их в своей строке.

Теперь для замены в определенных местах индекса вы можете использовать:

public static String replaceStringAt(String s, int pos, String c) {
   return s.substring(0,pos) + c + s.substring(pos+1);
}

В то время как pos - это индекс, где начинаются ваши строки (из приведенных выше индексных таблиц). Итак, скажем, вы создали две таблицы индексов для каждого из них. Назовем их indexBar и indexFoo.

Теперь, заменив их, вы можете просто запустить две петли, по одной для каждой замены, которую вы хотите сделать.

for(int i=0;i<indexBar.Count();i++)
replaceStringAt(originalString,indexBar[i],newString);

Аналогично другой цикл для indexFoo.

Это может быть не так эффективно, как другие ответы здесь, но это проще понять, чем Карты или другие вещи.

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

Также этот ответ не требует рекурсии и внешних зависимостей. Что касается сложности, то это возможно, когда O (n квадрат), а n - сумма вхождений обоих слов.

Ответ 19

Рассмотрим использование StringBuilder

Затем сохраните индекс, где должна начинаться каждая строка. Если вы используете символ места размещения в каждой позиции, удалите его и вставьте строку пользователя. Затем вы можете сопоставить конечную позицию, добавив длину строки в начальную позицию.

String firstString = "???";
String secondString  = "???"

StringBuilder story = new StringBuilder("One upon a time, there was a " 
    + firstString
    + " and a "
    + secondString);

int  firstWord = 30;
int  secondWord = firstWord + firstString.length() + 7;

story.replace(firstWord, firstWord + firstString.length(), userStringOne);
story.replace(secondWord, secondWord + secondString.length(), userStringTwo);

firstString = userStringOne;
secondString = userStringTwo;

return story;

Ответ 20

Я могу поделиться только своим собственным методом.

Вы можете использовать временные String temp = "<?>"; или String.Format();

Это мой примерный код, созданный в консольном приложении через - "Только идея, неточный ответ".

static void Main(string[] args)
    {
        String[] word1 = {"foo", "Once"};
        String[] word2 = {"bar", "time"};
        String story = "Once upon a time, there was a foo and a bar.";

        story = Switcher(story,word1,word2);
        Console.WriteLine(story);
        Console.Read();
    }
    // Using a temporary string.
    static string Switcher(string text, string[] target, string[] value)
    {
        string temp = "<?>";
        if (target.Length == value.Length)
        {
            for (int i = 0; i < target.Length; i++)
            {
                text = text.Replace(target[i], temp);
                text = text.Replace(value[i], target[i]);
                text = text.Replace(temp, value[i]);
            }
        }
        return text;
    }

Или вы также можете использовать String.Format();

static string Switcher(string text, string[] target, string[] value)
        {
            if (target.Length == value.Length)
            {
                for (int i = 0; i < target.Length; i++)
                {
                    text = text.Replace(target[i], "{0}").Replace(value[i], "{1}");
                    text = String.Format(text, value[i], target[i]);
                }
            }
            return text;
        }

Вывод: time upon a Once, there was a bar and a foo.

Ответ 21

Здесь моя версия, основанная на словах:

class TextReplace
{

    public static void replaceAll (String text, String [] lookup,
                                   String [] replacement, String delimiter)
    {

        String [] words = text.split(delimiter);

        for (int i = 0; i < words.length; i++)
        {

            int j = find(lookup, words[i]);

            if (j >= 0) words[i] = replacement[j];

        }

        text = StringUtils.join(words, delimiter);

    }

    public static  int find (String [] array, String key)
    {

        for (int i = 0; i < array.length; i++)
            if (array[i].equals(key))
                return i;

        return (-1);

    }

}

Ответ 22

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."

Маленький сложный путь, но вам нужно сделать еще несколько проверок.

1. преобразовать строку в массив символов

   String temp[] = story.split(" ");//assume there is only spaces.

2.loop на temp и замените foo на bar и bar на foo, поскольку нет никаких шансов получить заменяемую строку снова.

Ответ 23

Ну, более короткий ответ...

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar.";
story = story.replace("foo", "@"+ word1).replace("bar", word2).replace("@" + word2, word1);
System.out.println(story);

Ответ 24

Я разработал этот код, решив проблему:

public static String change(String s,String s1, String s2) {
   int length = s.length();
   int x1 = s1.length();
   int x2 = s2.length();
   int x12 = s.indexOf(s1);
   int x22 = s.indexOf(s2);
   String s3=s.substring(0, x12);
   String s4 =s.substring(x12+3, x22);
   s=s3+s2+s4+s1;
   return s;
}

В основном использовании change(story,word2,word1).

Ответ 25

String word1 = "bar";
String word2 = "foo";

String story = "Once upon a time, there was a foo and a bar."

story = story.replace("foo", "<foo />");
story = story.replace("bar", "<bar />");

story = story.replace("<foo />", word1);
story = story.replace("<bar />", word2);