如何以一个不会最终替换另一个字符串的方式替换两个字符串?
Posted
技术标签:
【中文标题】如何以一个不会最终替换另一个字符串的方式替换两个字符串?【英文标题】:How can I replace two strings in a way that one does not end up replacing the other? 【发布时间】:2015-01-03 15:59:17 【问题描述】:假设我有以下代码:
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 肯定应该有一些函数swap(String s1, String s2, String s3)
将所有出现的s2
与s3
交换,反之亦然。
我们可以假设输入中每个可交换的词只出现一次吗?
极端情况:在“ababababababa”中交换“ab”和“ba”时,我们期望输出什么?
您在下面有一些很好的解决方案,但是您了解为什么您的方法不起作用吗?首先,你有“有一个 foo 和一个酒吧”。在第一次替换 ("foo"->"bar") 之后,你有“有一个 bar 和一个 bar”。您现在出现了 2 次“bar”,因此您的第二次替换没有达到您的预期 - 它无法知道您只想替换上次没有替换的那个。 @HagenvonEitzen 有趣。我希望一个可行的解决方案能够匹配并替换它找到的任一字符串中的第一个,然后从替换部分的末尾重复。
Jeroen 的解决方案是我在文本编辑器中经常使用的解决方案,当我需要进行批量重命名时。它简单易懂,不需要特殊的库,稍加思考就可以万无一失。
【参考方案1】:
你使用了一个中间值(它还没有出现在句子中)。
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 in the comments给出的极好的建议:
不要巧妙地(愚蠢地)使用一些不太可能的字符串。使用来自 Unicode Private Use Area 的字符,U+E000..U+F8FF。首先删除任何此类字符,因为它们不应该合法地出现在输入中(它们仅在某些应用程序中具有特定于应用程序的含义),然后在替换时将它们用作占位符。
【讨论】:
@arshajii 我想这取决于你对“更好”的定义......如果它有效并且性能可以接受,那么继续下一个编程任务并在重构期间改进它是我的方法。 显然“lala”只是一个例子。在生产中,您应该使用“zq515sqdqs5d5sq1dqs4d1q5dqqé”&é&€sdq:d:;)àçàçlala”。 不要巧妙地(愚蠢地)使用一些不太可能的字符串。使用来自 Unicode Private Use Area 的字符,U+E000..U+F8FF。首先删除任何此类字符,因为它们不应该合法地出现在输入中(它们仅在某些应用程序中具有特定于应用程序的含义),然后在替换时将它们用作占位符。 其实看了Unicode FAQ on it之后,我认为U+FDD0..U+FDEF范围内的非字符会是一个更好的选择。 @Taemyr 当然,但必须有人清理输入,对吧?我希望字符串替换函数适用于所有字符串,但是这个函数会因不安全的输入而中断。【参考方案2】:你总是可以用一个你确定不会出现在字符串中其他地方的词来替换它,然后再做第二次替换:
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"
确实发生,这将无法正常工作。
【讨论】:
使用来自 Unicode Private Use Area 的字符,U+E000..U+F8FF,创建 StringThatCannotEverOccur。您可以预先过滤掉它们,因为它们不应该存在于输入中。 或U+FDD0..U+FDEF,“非字符”,保留供内部使用。【参考方案3】:你可以尝试这样的事情,使用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());
从前,有一个酒吧和一个foo。
【讨论】:
如果foo
、bar
和story
都有未知值,这是否有效?
@StephenP 我基本上硬编码了 "foo"
和 "bar"
替换字符串,就像 OP 在他的代码中一样,但是即使这些值不是相同类型的方法也可以正常工作已知(您必须在 while
-loop 中使用 if
/else if
而不是 switch
)。
您必须小心创建正则表达式。 Pattern.quote
会派上用场,或者 \Q
和 \E
。
@arshajii - 是的,我向自己证明了它是一种将 word1、word2 和故事作为参数的“swapThese”方法。 +1
更简洁的方法是使用模式(foo)|(bar)
,然后检查m.group(1) != null
,以避免重复匹配的单词。【参考方案4】:
这是一个 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);
【讨论】:
当您要替换的内容是由空格(或类似内容)分隔的实际 单词 时,这是一个不错的建议,但这不适用于替换单词的子字符串。 +1 用于 Java8 流。太糟糕了,这需要一个分隔符。【参考方案5】:使用Apache Commons StringUtils中的replaceEach()
方法:
StringUtils.replaceEach(story, new String[]"foo", "bar", new String[]"bar", "foo")
【讨论】:
知道 replaceEach 在内部到底做了什么吗? @Marek 该函数很可能会进行搜索并为找到的每个项目编制索引,然后在它们全部被索引后替换所有项目。 您可以在第 4684 行附近找到此 here 的来源。 虽然null
被传递,但很遗憾它是空操作。【参考方案6】:
我开发的这段代码将解决问题:
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).
【讨论】:
只有在每个字符串只有一次出现时才有效【参考方案7】:搜索要替换的第一个单词。如果它在字符串中,则在字符串出现之前的部分进行递归,在字符串出现之后的部分进行递归。
否则,继续下一个要替换的单词。
一个简单的实现可能看起来像这样
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 上找到它,以及此处发布的各种解决方案的一些粗略时间安排。
【讨论】:
虽然使用像 apache commons 这样的现有库来解决这个相当常见的问题无疑是解决这个相当普遍的问题的最简单方法,但您已经展示了一个适用于部分单词、运行时决定的单词的实现并且没有用魔术标记替换子字符串,这与(当前)更高投票的答案不同。 +1 很漂亮,但在提供 100 mb 的输入文件时会掉到地上。【参考方案8】:如果您希望能够处理多次出现的要替换的搜索字符串,您可以通过在每个搜索词上拆分字符串然后替换它来轻松做到这一点。 这是一个例子:
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;
【讨论】:
【参考方案9】:只交换一次出现
如果输入中每个可交换字符串只出现一次,您可以执行以下操作:
在进行任何替换之前,获取单词出现的索引。之后,我们只替换在这些索引中找到的单词,而不是所有出现的单词。该解决方案使用StringBuilder
,不会像String.replace()
那样产生中间的String
s。
需要注意的一点:如果可交换词的长度不同,则在第一次替换后,第二个索引可能会随着两个长度的差异而发生变化(如果第一个词出现在第二个之前)。因此,即使我们交换不同长度的单词,对齐第二个索引也能确保其正常工作。
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();
【讨论】:
我不确定 java 如何处理 unicode,但此代码的 C# 等效项是不正确的。问题是indexOf
匹配的子字符串的长度可能与搜索字符串的长度不同,这要归功于 unicode 字符串等价的特性。
@CodesInChaos 它在 Java 中完美运行,因为 Java String
是字符数组而不是字节数组。 String
和 StringBuilder
的所有方法都对字符而不是字节进行操作,它们是“无编码”的。因此indexOf
匹配具有与搜索字符串完全相同的(字符)长度。
在 C# 和 java 中,字符串都是 UTF-16 代码单元的序列。问题是存在 unicode 认为等效的不同代码点序列。例如ä
可以编码为单个代码点或a
后跟¨
的组合。还有一些代码点被忽略,例如零宽度(非)连接符。字符串是否由字节、字符或其他内容组成并不重要,但indexOf
使用哪个比较规则。它可能通过代码单元比较(“Ordinal”)简单地使用代码单元,或者它可能实现 unicode 等价。不知道java选了哪一个。
例如"ab\u00ADc".IndexOf("bc")
在.net中返回1
,匹配两个字符串bc
到一个三个字符串。
@CodesInChaos 我明白你现在的意思了。在 Java 中,"ab\u00ADc".indexOf("bc")
返回-1
,这意味着在"ab\u00ADc"
中找不到"bc"
。因此,在 Java 中,上述算法仍然有效,indexOf()
匹配的(字符)长度与搜索字符串完全相同,indexOf()
仅在字符序列(代码点)匹配时才报告匹配。【参考方案10】:
这不是一个简单的问题。你拥有的搜索替换参数越多,它就越棘手。您有几种选择,分散在丑陋-优雅、高效-浪费的调色板上:
建议使用来自 Apache Commons 的 StringUtils.replaceEach
@AlanHay。如果您可以在项目中随意添加新的依赖项,这是一个不错的选择。您可能会很幸运:依赖项可能已经包含在您的项目中
按照@Jeroen 的建议使用临时占位符,并分两步执行替换:
-
用原文中不存在的唯一标签替换所有搜索模式
用真正的目标替换替换占位符
这不是一个好方法,有几个原因:它需要确保第一步中使用的标签确实是唯一的;它执行了比实际需要更多的字符串替换操作
根据@arshajii 的建议,从所有模式构建一个正则表达式并使用Matcher
and StringBuffer
的方法。这并不可怕,但也不是那么好,因为构建正则表达式有点骇人听闻,它涉及到StringBuffer
,它在不久前已经过时,而支持StringBuilder
。
使用@mjolka 提出的递归解决方案,通过在匹配的模式处拆分字符串,并在剩余的段上递归。这是一个很好的解决方案,紧凑且非常优雅。它的弱点是潜在的许多子字符串和连接操作,以及适用于所有递归解决方案的堆栈大小限制
按照@msandiford 的建议,将文本拆分为单词并使用 Java 8 流来优雅地执行替换,但当然,这只有在您可以在单词边界处拆分时才有效,这使得它不适合作为一般解决方案
这是我的版本,基于从 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<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")
);
【讨论】:
【参考方案11】: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());
环视正则表达式(?<=
、?=
):http://www.regular-expressions.info/lookaround.html
如果单词可以包含特殊的正则表达式字符,请使用Pattern.quote
逃离他们。
为了简洁起见,我使用 guava ImmutableMap,但显然任何其他 Map 也可以完成这项工作。
【讨论】:
【参考方案12】:如果您想替换句子中由空格分隔的单词,如示例中所示,您可以使用这个简单的算法。
-
在空白处拆分故事
替换每个元素,如果 foo 将其替换为 bar,则替换为 varsa
将数组重新组合成一个字符串
如果在空间上拆分是不可接受的,则可以遵循此替代算法。您需要先使用较长的字符串。如果字符串是foo和傻瓜,则需要先使用傻瓜,然后再使用foo。
-
在单词 foo 上拆分
将 bar 替换为 foo 数组的每个元素
加入该数组,在除最后一个元素之外的每个元素之后添加条形
【讨论】:
这也是我想建议的。虽然它增加了一个限制,即文本是用空格包围的单词。 :) @MariusŽilėnas 我添加了另一种算法。【参考方案13】:使用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 之于仓鼠
但是反转替换:
truth += " as " + simultaneousReplace(truth,
"Java", "Ham",
"JavaScript", "Hamster");
输出:
Java 之于 JavaScript 就像 Ham 之于 HamScript
哎呀! :)
因此,确保查找 最长 匹配有时很有用(例如,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
参数。
【讨论】:
【参考方案14】:这是一个使用 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;
方法被调用
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);
输出是: 拉菲真棒,拉菲拉菲真棒棒棒哒
【讨论】:
在此之后运行replaced.replaceAll("Raffy", "Barney");
将使其成为传奇......等待它;达里!!!【参考方案15】:
使用找到的答案here,您可以找到您希望替换的所有字符串。
例如,您运行上述 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 squared),而 n 是两个单词出现次数的总和。
【讨论】:
【参考方案16】:如果您不想要任何依赖项,您可以简单地使用一个只允许一次性更改的数组。这不是最有效的解决方案,但应该可以。
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.
【讨论】:
【参考方案17】:考虑使用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;
【讨论】:
【参考方案18】:我只能分享我自己的方法。
您可以使用临时的String temp = "<?>";
或String.Format();
这是我通过c# 在控制台应用程序中创建的示例代码-“仅想法,不准确答案”。
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.
【讨论】:
这很老套。如果他要替换“_”,你会怎么做? @Pier-AlexandreBouchard 在方法中,我将temp
的值从"_"
更改为<?>
。但是如果需要,他可以做的是在方法中添加另一个参数来改变温度。 -“最好保持简单对吧?”
我的意思是,你不能保证预期的结果,因为如果 temp == 替换,你的方式将行不通。【参考方案19】:
您可以使用以下代码块实现您的目标:
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" ));
【讨论】:
【参考方案20】: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);
【讨论】:
【参考方案21】:您正在对输入执行多个搜索替换操作。当替换字符串包含搜索字符串时,这将产生不希望的结果。考虑 foo->bar, bar-foo 的例子,这里是每次迭代的结果:
-
曾几何时,有一个 foo 和一个 bar。 (输入)
从前,有一个酒吧和一个酒吧。 (foo->bar)
曾几何时,有一个 foo 和一个 foo。 (bar->foo, 输出)
您需要在一次迭代中执行替换而不返回。蛮力解决方案如下:
-
在输入中从当前位置到结尾搜索多个搜索字符串,直到找到匹配项
用对应的替换字符串替换匹配的搜索字符串
将当前位置设置为替换字符串后的下一个字符
重复
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
Demo on IDEONEDemo on IDEONE, alternate code
【讨论】:
【参考方案22】:这是我的版本,它是基于单词的:
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);
【讨论】:
【参考方案23】: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 on temp 并将foo
替换为bar
并将bar
替换为foo
,因为没有机会再次获得可替换的字符串。
【讨论】:
【参考方案24】:这很有效,而且很简单:
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);
【讨论】:
【参考方案25】:嗯,简短的答案是……
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);
【讨论】:
以上是关于如何以一个不会最终替换另一个字符串的方式替换两个字符串?的主要内容,如果未能解决你的问题,请参考以下文章