当替换文本与搜索文本重叠时,用Java替换多个子字符串

时间:2011-09-23 16:22:21

标签: java string replace

假设您有以下字符串:

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. (cat - > dog)dog dog fish dog fish dog
  3. (dog - > fish)fish fish fish fish fish fish
  4. (fish - > cat)cat cat cat cat cat cat
  5. 显然,这不是预期的结果。那么最简单的方法是什么?我可以和PatternMatcher(以及很多Pattern.quote()Matcher.quoteReplacement())一起拼凑一些东西,但我拒绝相信我是第一个遇到这个问题的人并且没有库函数来解决它。

    (FWIW,实际案例有点复杂,不涉及直接交换。)

5 个答案:

答案 0 :(得分:8)

apache commons中似乎StringUtils.replaceEach可以满足您的需求:

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

请注意,上述链接中的文档似乎有误。有关详细信息,请参阅下面的评论。

答案 1 :(得分:7)

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

输出:

  狗鱼猫猫猫狗

显然这个解决方案只对许多替代品有意义,否则这是一个巨大的矫枉过正。

答案 2 :(得分:4)

我会创建一个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());
   }
}

答案 3 :(得分:0)

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

输出: - Apple Banana Cantalope Date Apple Banana Cantalope Date

答案 4 :(得分:0)

这是一种无需使用正则表达式的方法。

我注意到,每次将字符串a的一部分替换为b时,b将始终是最终字符串的一部分。因此,从此以后,您就可以忽略字符串中的b

不仅如此,在将a替换为b之后,还有一个“空格”。在应该b的位置上无法进行任何替换。

这些动作加起来看起来很像splitsplit增大值(在字符串之间形成“空格”),对数组中的每个字符串进行进一步的替换,然后将它们重新连接起来。

例如:

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

我认为这不是很有效。

相关问题