java 如何以一种最终不会替换另一个字符串的方式替换两个字符串?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26791441/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 10:36:51  来源:igfitidea点击:

How can I replace two strings in a way that one does not end up replacing the other?

javastringreplace

提问by Pikamander2

Let's say that I have the following code:

假设我有以下代码:

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);

After this code runs, the value of storywill be "Once upon a time, there was a foo and a foo."

这段代码运行后,的值story将是"Once upon a time, there was a foo and a foo."

A similar issue occurs if I replaced them in the opposite order:

如果我以相反的顺序替换它们,则会出现类似的问题:

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);

The value of storywill be "Once upon a time, there was a bar and a bar."

的价值story"Once upon a time, there was a bar and a bar."

My goal is to turn storyinto "Once upon a time, there was a bar and a foo."How could I accomplish that?

我的目标是story变成"Once upon a time, there was a bar and a foo."我怎样才能做到这一点?

采纳答案by Alan Hay

Use the replaceEach()method from Apache Commons StringUtils:

使用Apache Commons StringUtils 中replaceEach()方法:

StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})

回答by Jeroen Vannevel

You use an intermediate value (which is not yet present in the sentence).

您使用了一个中间值(句子中尚未出现)。

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

As a response to criticism: if you use a large enough uncommon string like zq515sqdqs5d5sq1dqs4d1q5dqqé"&é5d4sqjshsjddjhodfqsqc, nvùq^μù;d&sdq: d: ;)à?à?lalaand use that, it is unlikely to the point where I won't even debate it that a user will ever enter this. The only way to know whether a user will is by knowing the source code and at that point you're with a whole other level of worries.

作为对批评的回应:如果您使用足够大的不常见字符串,例如zq515sqdqs5d5sq1dqs4d1q5dqqé"&é5d4sqjshsjddjhodfqsqc, nvùq^μù;d&sdq: d: ;)à?à?lala甚至不太可能使用它争论用户是否会输入这个。知道用户是否会输入的唯一方法是了解源代码,然后你就会有另一种担忧。

Yes, maybe there are fancy regex ways. I prefer something readable that I know will not break out on me either.

是的,也许有花哨的正则表达式方式。我更喜欢可读的东西,我知道它也不会在我身上爆发。

Also reiterating the excellent advise given by @David Conrad in the comments:

还重申@David Conrad 在评论中给出的出色建议:

Don't use some string cleverly (stupidly) chosen to be unlikely. Use characters from the Unicode Private Use Area, U+E000..U+F8FF. Remove any such characters first, since they shouldn't legitimately be in the input (they only have application-specific meaning within some application), then use them as placeholders when replacing.

不要巧妙地(愚蠢地)使用一些不太可能的字符串。使用 Unicode 专用区中的字符,U+E000..U+F8FF。首先删除任何此类字符,因为它们不应该合法地出现在输入中(它们在某些应用程序中仅具有特定于应用程序的含义),然后在替换时将它们用作占位符。

回答by arshajii

You can try something like this, using Matcher#appendReplacementand Matcher#appendTail:

你可以尝试这样的事情,使用Matcher#appendReplacementMatcher#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.

回答by janos

This is not an easy problem. And the more search-replacement parameters you have, the trickier it gets. You have several options, scattered on the palette of ugly-elegant, efficient-wasteful:

这不是一个容易的问题。您拥有的搜索替换参数越多,它就越棘手。你有几个选择,分散在丑陋优雅、高效浪费的调色板上:

  • Use StringUtils.replaceEachfrom Apache Commons as @AlanHayrecommended. This is a good option if you're free to add new dependencies in your project. You might get lucky: the dependency might be included already in your project

  • Use a temporary placeholder as @Jeroensuggested, and perform the replacement in 2 steps:

    1. Replace all search patterns with a unique tag that doesn't exist in the original text
    2. Replace the placeholders with the real target replacement

    This is not a great approach, for several reasons: it needs to ensure that the tags used in the first step are really unique; it performs more string replacement operations than really necessary

  • Build a regex from all the patterns and use the method with Matcherand StringBufferas suggested by @arshajii. This is not terrible, but not that great either, as building the regex is kind of hackish, and it involves StringBufferwhich went out of fashion a while ago in favor of StringBuilder.

  • Use a recursive solution proposed by @mjolka, by splitting the string at the matched patterns, and recursing on the remaining segments. This is a fine solution, compact and quite elegant. Its weakness is the potentially many substring and concatenation operations, and the stack size limits that apply to all recursive solutions

  • Split the text to words and use Java 8 streams to perform the replacements elegantly as @msandifordsuggested, but of course that only works if you are ok with splitting at word boundaries, which makes it not suitable as a general solution

  • 使用StringUtils.replaceEach来自Apache的百科全书@AlanHay建议。如果您可以自由地在项目中添加新的依赖项,这是一个不错的选择。您可能会很幸运:依赖项可能已经包含在您的项目中

  • 使用@Jeroen建议的临时占位符,并分两步执行替换:

    1. 用原始文本中不存在的唯一标签替换所有搜索模式
    2. 用真正的目标替换替换占位符

    这不是一个很好的方法,有几个原因:它需要确保第一步中使用的标签确实是唯一的;它执行比真正需要的更多的字符串替换操作

  • 从所有模式构建一个正则表达式,MatcherStringBuffer按照@arshajii 的建议使用该方法。这并不可怕,但也不是那么好,因为构建正则表达式有点hackish,并且它涉及StringBuffer不久前已经过时而支持StringBuilder.

  • 使用@mjolka提出的递归解决方案,通过在匹配的模式处拆分字符串,并在剩余的段上递归。这是一个很好的解决方案,紧凑且非常优雅。它的弱点是潜在的许多子串和连接操作,以及适用于所有递归解决方案的堆栈大小限制

  • 将文本拆分为单词并使用 Java 8 流按照@msandiford 的建议优雅地执行替换,但当然只有在您可以在单词边界进行拆分时才有效,这使得它不适合作为通用解决方案

Here's my version, based on ideas borrowed from Apache's implementation. It's neither simple nor elegant, but it works, and should be relatively efficient, without unnecessary steps. In a nutshell, it works like this: repeatedly find the next matching search pattern in the text, and use a StringBuilderto accumulate the unmatched segments and the replacements.

这是我的版本,基于从Apache 的 implementation借用的想法。它既不简单也不优雅,但它有效,并且应该相对高效,没有不必要的步骤。简而言之,它的工作原理是:在文本中重复查找下一个匹配的搜索模式,并使用 aStringBuilder来累积未匹配的段和替换。

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;
    }
}

Unit tests:

单元测试:

@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"})
    );
}

回答by mjolka

Search for the first word to be replaced. If it's in the string, recurse on the the part of the string before the occurrence, and on the part of the string after the occurrence.

搜索要替换的第一个单词。如果它在字符串中,则对出现之前的字符串部分和出现之后的字符串部分进行递归。

Otherwise, continue with the next word to be replaced.

否则,继续下一个要替换的单词。

A naive implementation might look like this

一个简单的实现可能看起来像这样

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);
}

Sample usage:

示例用法:

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));

Output:

输出:

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


A less-naive version:

一个不太天真的版本:

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);
}

Unfortunately, Java's Stringhas no indexOf(String str, int fromIndex, int toIndex)method. I've omitted the implementation of indexOfhere as I'm not certain it's correct, but it can be found on ideone, along with some rough timings of various solutions posted here.

不幸的是,JavaString没有indexOf(String str, int fromIndex, int toIndex)方法。我省略了indexOf此处的实现,因为我不确定它是否正确,但可以在ideone找到,以及此处发布的各种解决方案的一些粗略时间。

回答by Vitalii Fedorenko

One-liner in Java 8:

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());

回答by clstrfsck

Here is a Java 8 streams possibility that might be interesting for some:

这是 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);


Here is an approximation of the same algorithm in Java 7:

这是 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);

回答by fastcodejava

?If you want to replace words in a sentence which are separated by white space as shown in your example you can use this simple algorithm.

?如果您想替换句子中由空格分隔的单词,如您的示例所示,您可以使用这个简单的算法。

  1. Split story on white space
  2. Replace each elements, if foo replace it to bar and vice varsa
  3. Join the array back into one string
  1. 在空白处拆分故事
  2. 替换每个元素,如果 foo 将其替换为 bar,反之亦然
  3. 将数组连接回一个字符串

?If Splitting on space is not acceptable one can follow this alternate algorithm. ?You need to use the longer string first. If the stringes are foo and fool, you need to use fool first and then foo.

?如果空间上的拆分是不可接受的,则可以遵循此替代算法。?您需要先使用较长的字符串。如果字符串是foo 和fool,则需要先使用fool,然后使用foo。

  1. Split on the word foo
  2. Replace bar with foo each element of the array
  3. Join that array back adding bar after each element except the last
  1. 拆分单词 foo
  2. 用 foo 替换 bar 数组的每个元素
  3. 在除最后一个元素之外的每个元素之后加入该数组

回答by WillingLearner

Here's a less complicated answer using Map.

这是使用 Map 的一个不太复杂的答案。

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;
     }

And method is called

方法被称为

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);

Output is: awesome is Raffy, Raffy Raffy is awesome awesome

输出是:Raffy真棒,Raffy Raffy真棒

回答by ventsyv

If you want to be able to handle multiple occurrences of the search strings to be replaced, you can do that easily by splitting the string on each search term, then replacing it. Here is an example:

如果您希望能够处理要替换的搜索字符串的多次出现,您可以通过在每个搜索词上拆分字符串然后替换它来轻松实现。下面是一个例子:

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;
}