假设我有以下代码:
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."
我怎样才能做到这一点?
swap(String s1, String s2, String s3)
将所有出现的 s2
与 s3
交换,反之亦然。
使用 Apache Commons StringUtils 中的 replaceEach()
方法:
StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})
您使用中间值(句子中尚未出现)。
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。首先删除任何此类字符,因为它们不应该合法地出现在输入中(它们仅在某些应用程序中具有特定于应用程序的含义),然后在替换时将它们用作占位符。
您可以使用 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());
Once upon a time, there was a bar and a foo.
foo
、bar
和 story
都具有未知值,这是否有效?
"foo"
和 "bar"
替换字符串,但是即使这些值不知道,相同类型的方法也可以正常工作(你必须使用if
/else if
而不是 while
循环中的 switch
)。
Pattern.quote
或 \Q
和 \E
会派上用场。
(foo)|(bar)
然后检查 m.group(1) != null
,以避免重复匹配的单词。
这不是一个容易的问题。你拥有的搜索替换参数越多,它就越棘手。你有几个选择,分散在丑陋-优雅、高效-浪费的调色板上:
按照@AlanHay 的建议,使用来自 Apache Commons 的 StringUtils.replaceEach。如果您可以在项目中随意添加新的依赖项,这是一个不错的选择。您可能会很幸运:依赖项可能已经包含在您的项目中
使用@Jeroen 建议的临时占位符,并分两步执行替换: 用原始文本中不存在的唯一标签替换所有搜索模式 用真正的目标替换替换占位符 这不是一个好方法,因为几个原因:需要保证第一步使用的标签真的是唯一的;它执行了比实际需要更多的字符串替换操作
将所有搜索模式替换为原始文本中不存在的唯一标签
用真正的目标替换替换占位符
从所有模式构建一个正则表达式,并按照@arshajii 的建议使用带有 Matcher 和 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"})
);
}
搜索要替换的第一个单词。如果它在字符串中,则在出现之前的字符串部分上递归,在出现之后的字符串部分上递归。
否则,继续下一个要替换的单词。
一个天真的实现可能看起来像这样
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 上找到它,以及此处发布的各种解决方案的一些粗略时间安排。
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 也可以完成这项工作。
这是一个 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);
如果您想替换句子中由空格分隔的单词,如示例中所示,您可以使用这个简单的算法。
在空白处拆分故事替换每个元素,如果 foo 将其替换为 bar 和副 varsa 将数组重新加入一个字符串
如果在空间上拆分是不可接受的,则可以遵循此替代算法。您需要先使用较长的字符串。如果字符串是foo和傻瓜,则需要先使用傻瓜,然后再使用foo。
拆分单词 foo 将 bar 替换为 foo 数组的每个元素加入该数组,在除最后一个元素之外的每个元素之后添加 bar
这是使用 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);
输出是:awesome is Raffy, Raffy Raffy is awesome awesome
replaced.replaceAll("Raffy", "Barney");
将使其成为传奇......等待它;达里!!!
如果您希望能够处理要替换的搜索字符串的多次出现,您可以通过在每个搜索词上拆分字符串然后替换它来轻松地做到这一点。这是一个例子:
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;
}
您可以使用以下代码块来实现您的目标:
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" }));
这很有效并且很简单:
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);
}
仅交换一次出现
如果输入中每个可交换字符串只出现一次,您可以执行以下操作:
在进行任何替换之前,获取单词出现的索引。之后,我们只替换在这些索引中找到的单词,而不是所有出现的单词。此解决方案使用 StringBuilder
并且不会产生像 String.replace()
这样的中间 String
。
需要注意的一点:如果可交换的词具有不同的长度,则在第一个替换之后,第二个索引可能会发生变化(如果第一个词出现在第二个之前),恰好与 2 个长度的差异。因此,即使我们交换不同长度的单词,对齐第二个索引也将确保它有效。
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();
}
indexOf
匹配的子字符串的长度可能与搜索字符串的长度不同。
String
是字符数组而不是字节数组。 String
和 StringBuilder
的所有方法都对字符而不是字节进行操作,它们是“无编码”的。因此 indexOf
匹配具有与搜索字符串完全相同的(字符)长度。
ä
可以编码为单个代码点,也可以编码为 a
,然后是组合 ¨
。还有一些代码点被忽略,例如零宽度(非)连接符。字符串是否由字节、字符或其他组成无关紧要,但使用哪种比较规则 indexOf
并不重要。它可能通过代码单元比较(“Ordinal”)简单地使用代码单元,或者它可能实现 unicode 等价。我不知道选择了哪一个java。
"ab\u00ADc".IndexOf("bc")
在 .net 中返回 1
,将两个字符串 bc
匹配为三个字符串。
"ab\u00ADc".indexOf("bc")
返回 -1
,这意味着在 "ab\u00ADc"
中找不到 "bc"
。因此,在 Java 中,上述算法仍然有效,indexOf()
匹配具有与搜索字符串完全相同的(字符)长度,并且 indexOf()
仅在字符序列(代码点)匹配时才报告匹配。
使用 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
参数。
如果您不想要任何依赖项,您可以简单地使用一个只允许一次性更改的数组。这不是最有效的解决方案,但它应该可以工作。
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.
您正在对输入执行多个搜索替换操作。当替换字符串包含搜索字符串时,这将产生不希望的结果。考虑 foo->bar, bar-foo 的例子,这里是每次迭代的结果:
曾几何时,有一个 foo 和一个酒吧。 (输入)从前,有一个酒吧和一个酒吧。 (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 IDEONE
Demo on IDEONE, alternate code
您始终可以将其替换为您确定不会出现在字符串中其他位置的单词,然后稍后再进行第二次替换:
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"
,这将无法正常工作。
考虑使用 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;
我只能分享的是我自己的方法。
您可以使用临时的 String temp = "<?>";
或 String.Format();
这是我在控制台应用程序中通过 c# -“Idea Only,Not Exact Answer” 创建的示例代码。
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.
temp
的值从 "_"
更改为 <?>
。但是如果需要,他可以做的是在方法中添加另一个参数来改变温度。 -“最好保持简单对吧?”
这是我的版本,它是基于单词的:
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);
}
}
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.在 temp 上循环并将 foo
替换为 bar
并将 bar
替换为 foo
,因为没有机会再次获得可替换的字符串。
嗯,简短的答案是......
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);
使用找到的答案 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 是两个单词出现次数的总和。
我开发的这段代码将解决问题:
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).
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);
不定期副业成功案例分享
null
被传递时,它是空操作。