如何替换两个字符串,最终不会替换另一个字符串?

问题:

假设我有以下代码:

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."我该怎么做?

回答:

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

  • @AlanHay推荐使用Apache Commons中的StringUtils.replaceEach。如果您可以在项目中自由添加新的依赖项,这是一个很好的选择。您可能会幸运:依赖项可能已经包含在您的项目中
  • 使用@Jeroen建议的临时占位符,并按以下两个步骤执行替换:
     

    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

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

  • 从所有模式构建正则表达式,并按@arshajii的建议使用Matcher and StringBuffer的方法。这不是可怕的,但也不是那么伟大,因为构建正则表达式是一种黑客,它涉及到StringBuffer之前的时尚,StringBuilder
  • 使用由@mjolka提出的递归解,通过将匹配的模式分割为字符串,并在剩余的段上进行递归。这是一个很好的解决方案,紧凑和相当优雅。其弱点是潜在的许多子串和连接操作,以及适用于所有递归解决方案的堆栈大小限制
  • 将文本拆分为单词,并使用Java 8流,按@msandiford建议优雅地执行替换,但当然,只有在字符边界分割时才可行,这使得它不适合作为一般解决方案

这是我的版本,基于从Apache’s implementation借来的想法。它既不简单也不优雅,但它的作品,应该是比较有效的,没有不必要的步骤。简而言之,它的工作原理如下:在文本中重复找到下一个匹配的搜索模式,并使用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 patternToReplacement = new HashMap<>();
private final Set 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; } } [/code] 单位测试: [code lang="python"] @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"}) ); } [/code]     Code问答: http://codewenda.com/topics/python/
Stackoverflow: How can I replace two strings in a way that one does not end up replacing the other?

*转载请注明本文链接以及stackoverflow的英文链接

发表评论

电子邮件地址不会被公开。 必填项已用*标注

39 + = 42