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

Pik*_*er2 160 java string replace

假设我有以下代码:

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);
Run Code Online (Sandbox Code Playgroud)

此代码运行后,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);
Run Code Online (Sandbox Code Playgroud)

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

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

Jer*_*vel 87

您使用中间值(句子中尚未出现).

story = story.replace("foo", "lala");
story = story.replace("bar", "foo");
story = story.replace("lala", "bar");
Run Code Online (Sandbox Code Playgroud)

作为对批评的回应:如果你使用一个足够大的罕见字符串如zq515sqdqs5d5sq1dqs4d1q5dqqé"&é5d4sqjshsjddjhodfqsqc,nvùq^μU; d&€SDQ:d:;)àçàçlala和使用,这是不可能的地步,我甚至不会展开辩论用户将进入此状态.了解用户是否通过了解源代码的唯一方法是,您可能会遇到其他问题.

是的,也许有花哨的正则表达方式.我更喜欢可读的东西,我知道也不会突破我.

同时重申@David Conrad在评论中提出的出色建议:

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

  • 不要巧妙地(愚蠢地)使用某些字符串.使用Unicode专用区域中的字符,U + E000..U + F8FF.首先删除任何此类字符,因为它们不应合法地在输入中(它们在某些应用程序中仅具有特定于应用程序的含义),然后在替换时将它们用作占位符. (81认同)
  • 这似乎是一种非常黑客的做法.当然有更好的方法...... (40认同)
  • 显然,"lala"只是一个例子.在制作中,您应该使用"*zq515sqdqs5d5sq1dqs4d1q5dqqé"&é&€sdq:d:;)àçàçlala*". (24认同)
  • 实际上,在阅读[Unicode FAQ]之后(http://www.unicode.org/faq/private_use.html),我认为U + FDD0..U + FDEF范围内的非字符将是更好的选择. (22认同)
  • @Taemyr当然,但是有人必须对输入进行消毒,对吧?我希望字符串替换函数适用于所有字符串,但此函数会破坏不安全的输入. (6认同)
  • @arshajii我想这取决于你对"更好"的定义...如果它有效并且具有可接受的性能,那么继续下一个编程任务并在重构过程中改进它将是我的方法. (4认同)
  • 我考虑过这样做,但在我的实际程序中,`word1`和`word2`将由用户定义.如果用户要为word1输入`lala`,则会出现同样的问题. (4认同)
  • 优点@David - 在我们说话时改变我的代码. (3认同)
  • 此外,他们建议替换应用程序在内部使用的字符,因此如果它们在输入中,可能会被U + FFFD混淆,而不是简单地删除它们. (3认同)
  • 保证不在输入中的字符串只能使用已知的输入生成:更改字符串中每个字符的大小写是明显的.最终得到一个与输入长度相同的字符串,并且不同. (3认同)
  • @ Pikamander2:那是一个重要的评论.随着这句话的补充,arshajii的答案是最好的.但对于所有其他情况,这应该没问题.您仍然可以随机生成字符,而不是硬编码字符串. (2认同)
  • 你为什么要这样做?:(你现在必须记录你的方法不起作用的字符. (2认同)

Ala*_*Hay 87

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

StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})
Run Code Online (Sandbox Code Playgroud)

  • 你可以在第4684行找到这个[这里](http://commons.apache.org/proper/commons-lang/apidocs/src-html/org/apache/commons/lang3/StringUtils.html)的来源. (16认同)
  • 任何想法什么确切取代内部做什么? (2认同)
  • @Marek很可能该函数执行搜索并对找到的每个项目编制索引,然后在所有项目都被编入索引后替换所有项目. (2认同)

ars*_*jii 33

您可以尝试这样的事情,使用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());
Run Code Online (Sandbox Code Playgroud)
Once upon a time, there was a bar and a foo.

  • 你必须小心创建正则表达式.`Pattern.quote`会派上用场,或`\ Q`和`\ E`. (6认同)
  • 更简洁的是使用模式`(foo)|(bar)`然后检查`m.group(1)!= null`,以避免重复匹配的单词. (4认同)
  • 如果`foo`,`bar`和`story`都有未知值,这是否有效? (2认同)

Sto*_*ica 32

这不是一个容易的问题.而且你拥有的搜索替换参数越多,它就会变得越来越棘手.你有几个选项,分散在丑陋优雅,高效浪费的调色板上:

  • 使用StringUtils.replaceEachApache Commons @AlanHay推荐.如果您可以在项目中自由添加新的依赖项,这是一个不错的选择.您可能会很幸运:依赖项可能已包含在您的项目中

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

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

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

  • 建立从所有模式的正则表达式,并使用法Matcher,并StringBuffer通过建议@arshajii.这并不是很糟糕,但也不是那么好,因为建立正则表达式是一种hackish,它涉及到StringBuffer前一段时间已经过时了StringBuilder.

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

  • 将文本拆分为单词并使用Java 8流来优雅地执行替换,如@msandiford建议的那样,但当然只有在单词边界处分割时才有效,这使得它不适合作为一般解决方案

这是我的版本,基于从Apache的实现中借鉴的想法.它既不简单也不优雅,但它有效,并且应该相对有效,没有不必要的步骤.简而言之,它的工作方式如下:在文本中重复查找下一个匹配的搜索模式,并使用a 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

单元测试:

@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"})
    );
}
Run Code Online (Sandbox Code Playgroud)


mjo*_*lka 21

搜索要替换的第一个单词.如果它在字符串中,则在发生之前递归字符串的部分,并在发生之后递归字符串部分.

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

一个天真的实现可能看起来像这样

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);
}
Run Code Online (Sandbox Code Playgroud)

样品用法:

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));
Run Code Online (Sandbox Code Playgroud)

输出:

Once upon a foo, there was a bar and a baz.
Run Code Online (Sandbox Code Playgroud)

一个不太天真的版本:

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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 尽管像这样的事情使用像apache公共这样的现有库无疑是解决这个相当常见问题的最简单方法,但是你已经展示了一个实现,它可以在单词的部分上运行,在运行时决定的单词上,而不用魔术标记替换子串. (目前)更高的投票答案.+1 (2认同)

Vit*_*nko 12

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());
Run Code Online (Sandbox Code Playgroud)


msa*_*ord 11

以下是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);
Run Code Online (Sandbox Code Playgroud)

以下是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);
Run Code Online (Sandbox Code Playgroud)

  • 当您想要替换的内容是由空格(或类似)分隔的实际*单词*时,这是一个很好的建议,但这不适用于替换单词的子串. (10认同)

fas*_*ava 6

如果要替换由空格分隔的句子中的单词,如示例所示,则可以使用此简单算法.

  1. 在白色空间的分裂故事
  2. 如果foo将其替换为bar和副varsa,则替换每个元素
  3. 将数组加入一个字符串

如果不能接受在空间上拆分,则可以遵循此备用算法.您需要先使用较长的字符串.如果弦乐是愚蠢的,你需要先使用傻瓜然后再使用foo.

  1. 拆分字foo
  2. 用foo替换数组的每个元素
  3. 加入该数组后,在除最后一个元素之外的每个元素后添加条


Wil*_*ner 5

这是使用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;
     }
Run Code Online (Sandbox Code Playgroud)

并调用方法

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);
Run Code Online (Sandbox Code Playgroud)

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