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


162

假设我有以下代码:

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."我怎么能做到呢?


7
+1肯定应该有一些函数swap(String s1, String s2, String s3)可以s2s3和交换所有出现的内容,反之亦然。
瑞安2014年

我们是否可以假设输入中每个可交换单词仅出现一次?
icza 2014年

14
极端情况:在“ ababababababa”中交换“ ab”和“ ba”时,我们期望得到什么输出?
哈根·冯·埃森

1
您在下面有一些不错的解决方案,但是您了解为什么您的方法无效吗?首先,您有“有一个foo和一个酒吧”。第一次替换(“ foo”->“ bar”)后,您将看到“有一个酒吧和一个酒吧”。现在,您有2次出现“ bar”,因此您的第二次替换不能达到您的期望-无法知道您只希望替换上次未替换的替换。@HagenvonEitzen有趣。我希望一个可行的解决方案能够匹配并替换找到的任一字符串中的第一个,然后从被替换部分的末尾重复。
DeveloperInDevelopment

1
当需要批量重命名时,Jeroen的解决方案是我经常在文本编辑器中使用的解决方案。它简单,易于理解,不需要特殊的库,并且思想不拘一格。
热门点击2014年

Answers:


88

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

StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})

2
任何想法内部完全替换replaceEach吗?
Marek

3
@Marek函数很可能会进行搜索并为找到的每个项目建立索引,然后在全部被索引后将其替换。

16
你可以找到这个源头在这里周围排队4684.
吉荣Vannevel

可惜的是,它在null通过时是无人操作的。
2014年

87

您使用一个中间值(句子中尚不存在)。

story = story.replace("foo", "lala");
story = story.replace("bar", "foo");
story = story.replace("lala", "bar");

作为对批评的回应:如果使用足够大的不常见字符串,例如zq515sqdqs5d5sq1dqs4d1q5dqqé“ && 5d4sqjshsjddjhodfqsqc,nvùq^ µù; d&€sdq:d:;)àçàçlalala不会在哪争论,甚至不去使用它,知道用户是否会输入此信息的唯一方法就是知道源代码,这时您还需要其他一些担心。

是的,也许有花哨的正则表达式方式。我更喜欢可读性高的东西,我知道这些东西也不会爆发在我身上。

还重申@David Conrad在评论中给出的出色建议:

不要聪明地(愚蠢地)选择不太可能使用的字符串。使用Unicode专用区U + E000..U + F8FF中的字符。首先删除所有此类字符,因为它们不应合法地出现在输入中(它们在某些应用程序中仅具有特定于应用程序的含义),然后在替换时将它们用作占位符。


4
@arshajii我想这取决于您对“更好”的定义...如果它可以工作并且性能可接受,那么继续进行下一个编程任务,并在重构期间稍后对其进行改进将是我的方法。
Matt Coubrough 2014年

24
显然“ lala”只是一个例子。在生产中,应使用“ zq515sqdqs5d5sq1dqs4d1q5dqqé” &E&€SDQ:d:;)àçàçlala。”
吉荣Vannevel

81
不要聪明地(愚蠢地)选择不太可能使用的字符串。使用Unicode专用区U + E000..U + F8FF中的字符。首先删除所有此类字符,因为它们不应合法地出现在输入中(它们在某些应用程序中仅具有特定于应用程序的含义),然后在替换时将它们用作占位符。
David Conrad 2014年

22
实际上,在阅读了Unicode FAQ之后,我认为U + FDD0..U + FDEF范围内的非字符将是更好的选择。
戴维·康拉德

6
@Taemyr可以,但是必须清理输入内容,对吗?我希望所有的字符串都可以使用字符串替换功能,但是对于不安全的输入,此功能会中断。
纳文2014年

33

您可以使用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。

2
如果foobarstory都具有未知值,是否可以使用?
Stephen P

1
@StephenP我已经对OP "foo""bar"替换字符串进行了硬编码,就像OP在他的代码中一样,但是即使这些值未知,相同类型的方法也可以正常工作(您必须在if/ 内使用/ else if代替-循环)。switchwhile
arshajii 2014年

6
您必须小心创建正则表达式。Pattern.quote会派上用场,或\Q\E
戴维·康拉德

1
@arshajii-是的,以word1,word2和故事作为参数,以“ swapThese”方法向我证明了这一点。+1
Stephen P

4
甚至更干净的方法是使用该模式(foo)|(bar),然后对照进行检查m.group(1) != null,以避免重复单词以使其匹配。
约恩·霍斯特曼

32

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

  • 建议StringUtils.replaceEach从Apache Commons 使用@AlanHay。如果您可以随意在项目中添加新的依赖项,那么这是一个不错的选择。您可能会很幸运:依赖项可能已经包含在您的项目中

  • 按照@Jeroen的建议使用一个临时占位符,并通过2个步骤执行替换:

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

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

  • 从所有的模式建立一个正则表达式,并使用法Matcher,并StringBuffer通过建议@arshajii。这并不可怕,但也没有那么好,因为构建正则表达式有点黑,涉及StringBuffer前一阵子而过时的青睐StringBuilder

  • 使用@mjolka提出的递归解决方案,方法是将字符串拆分为匹配的模式,然后在其余段上递归。这是一个很好的解决方案,紧凑而优雅。它的弱点是可能有许多子字符串和串联操作,以及适用于所有递归解决方案的堆栈大小限制

  • 按照@msandiford的建议,将文本拆分为单词,并使用Java 8流优雅地执行替换操作,但是当然只有在可以在单词边界处拆分的情况下,该方法才有效,这使其不适合作为一般解决方案

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

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

用法示例:

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上找到它的实现,以及此处发布的各种解决方案的大致时间,因此省略了这里的实现。


2
尽管毫无疑问,使用诸如apache commons之类的现有库来解决此相当常见的问题是最简单的方法,但是您已经展示了一种实现方式,该实现方式可以处理单词的一部分,运行时确定的单词,而无需用魔术标记代替子字符串(目前)投票率较高的答案。+1
Buhb 2014年

很漂亮,但是当提供了100 mb的输入文件时会摔倒在地。
Christophe De Troyer 2014年

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

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

这是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);

10
当您要替换的内容是用空格(或类似字符)分隔的实际单词时,这是一个很好的建议,但这对于替换单词的子字符串不起作用。
西蒙·佛斯伯格

Java8流+1。太糟糕了,这需要定界符。
纳文2014年

6

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

  1. 在空白处拆分故事
  2. 替换每个元素,如果foo将其替换为bar,反之亦然
  3. 将数组连接回一个字符串

``如果不能在空间上拆分,则可以遵循此替代算法。``您需要先使用较长的字符串。如果字符串是foo和fool,则需要先使用fool,然后再使用foo。

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

1
这也是我当时想建议的。尽管它增加了一个限制,即文本是用空格包围的单词。:)
开发人员MariusŽilėnas2014年

@MariusŽilėnas我添加了替代算法。
fastcodejava 2014年

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

方法称为

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

输出是:太好了,拉菲,太子了太子了


1
replaced.replaceAll("Raffy", "Barney");在此之后运行会使其变色...等待它; 达利!
Keale 2014年

3

如果您希望能够处理多次出现的要替换的搜索字符串,则可以通过在每个搜索词上拆分字符串然后替换它来轻松实现。这是一个例子:

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

3

您可以使用以下代码块实现目标:

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

3

这有效且简单:

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

注意:这要依靠不包含character的String,String \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);
    }

3

只交换一次

如果输入中每个可交换字符串仅出现一次,则可以执行以下操作:

在进行任何替换之前,请获取单词出现的索引。之后,我们仅替换在这些索引中找到的单词,而不替换所有出现的单词。此解决方案使用StringBuilder且不会产生中间String的,例如String.replace()

需要注意的一件事:如果可交换单词的长度不同,则在第一次替换之后,第二个索引可能会发生变化(如果第一个单词出现在第二个单词之前),恰恰是两个长度的差。因此,即使我们要交换不同长度的单词,对齐第二个索引也可以确保此功能有效。

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

并使用此方法,通过减少索引(可能需要在2个可交换单词之间交替)来用另一个单词替换单词,这样我们甚至不必在替换后更正索引:

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 2014年

@CodesInChaos由于Java String是字符数组而不是字节数组,因此它在Java中可完美工作。字符的所有方法StringStringBuilder对字符而不是字节进行操作的字符都是“无编码的”。因此,indexOf匹配项与搜索字符串的长度(字符)完全相同。
icza 2014年

在C#和Java中,字符串都是UTF-16代码单元的序列。问题在于,unicode认为等效的代码点序列不同。例如,ä可以编码为单个代码点,也可以编码为a后跟组合¨。也有一些代码点被忽略,例如零宽度(非)连接符。字符串是否包含字节,字符或其他内容无关紧要,但是indexOf使用哪个比较规则。它可能仅使用逐个代码单位的比较(“序数”),或者可能实现unicode等效。我不知道选择哪个Java。
CodesInChaos

例如,.net中的"ab\u00ADc".IndexOf("bc")返回值1将两个字符串匹配bc为三个字符串。
CodesInChaos

1
@CodesInChaos我明白你的意思了。在Java中,"ab\u00ADc".indexOf("bc")返回-1表示"bc"未在中找到"ab\u00ADc"。因此,仍然可以证明上述算法在Java中有效, indexOf()匹配项的长度(字符)与搜索字符串完全相同,并且indexOf()仅在字符序列(代码点)匹配时才报告匹配项。
icza

2

使用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就像火腿对仓鼠一样

但撤消替换:

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参数。


2

如果您不希望有任何依赖关系,则可以只使用一个仅允许一次性更改的数组。这不是最有效的解决方案,但它应该可以工作。

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.

2

您正在输入上执行多个搜索替换操作。当替换字符串包含搜索字符串时,这将产生不希望的结果。考虑foo-> bar,bar-foo的示例,这是每次迭代的结果:

  1. 从前,那里有一个foo和一个酒吧。(输入)
  2. 从前,有一家酒吧。(foo-> bar)
  3. 从前,有一个foo和一个foo。(bar-> foo,输出)

您需要一次迭代执行替换操作,而无需返回。暴力破解解决方案如下:

  1. 从当前位置到输入的末尾搜索多个搜索字符串,直到找到匹配项
  2. 将匹配的搜索字符串替换为相应的替换字符串
  3. 将当前位置设置为替换字符串之后的下一个字符
  4. 重复

诸如此类的功能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

在IDEONE上进行演示在IDEONE 上进行
演示,备用代码


1

您始终可以用一个单词替换它,确保该单词不会出现在字符串中的其他任何地方,然后稍后再进行第二个替换:

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"确实发生,这将无法正常工作。


5
使用Unicode专用区U + E000..U + F8FF中的字符,创建一个StringThatCannotEverOccur。您可以事先过滤掉它们,因为它们不应该出现在输入中。
戴维·康拉德

或U + FDD0..U + FDEF,“非字符”,保留供内部使用。
戴维·康拉德

1

考虑使用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;

1

我只能分享的是我自己的方法。

您可以使用临时String temp = "<?>";String.Format();

这是我在控制台应用程序中通过创建的示例代码 -“仅想法,不是确切答案”

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.


这很hacky。如果他要替换“ _”,您将怎么办?
Pier-Alexandre Bouchard 2014年

@ Pier-AlexandreBouchard在方法中,我将tempfrom 的值更改"_"<?>。但是,如果需要,他可以做的就是在该方法中添加另一个参数来改变温度。-“最好保持简单吧?”
Leonel Sarmiento 2014年

我的观点是,yon无法保证预期的结果,因为如果temp == replace,您的方法将行不通。
Pier-Alexandre Bouchard 2014年

1

这是我的基于单词的版本:

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

    }

}

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.loop对温度和替换foobar,并barfoo因为没有得到再次替换字符串的机会。


1

好吧,简短的答案是...

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

1

使用此处找到的答案您可以找到所有希望替换为的字符串。

因此,例如,您可以在上述SO答案中运行代码。创建两个索引表(假设bar和foo在字符串中不会仅出现一次),您可以使用这些表替换字符串中的表。

现在,要替换特定的索引位置,您可以使用:

public static String replaceStringAt(String s, int pos, String c) {
   return s.substring(0,pos) + c + s.substring(pos+1);
}

pos字符串是从哪里开始的索引(来自我上面引用的索引表)。假设您为每个表创建了两个索引表。我们称它们为indexBarindexFoo

现在,在替换它们时,您可以简单地运行两个循环,每个要替换的循环一个。

for(int i=0;i<indexBar.Count();i++)
replaceStringAt(originalString,indexBar[i],newString);

同样,的另一个循环indexFoo

这可能不如此处的其他答案有效,但比Map或其他内容更容易理解。

这将始终为您提供所需的结果,并且每个字符串可能多次出现。只要您存储每次出现的索引。

同样,这个答案不需要递归,也不需要任何外部依赖。就复杂性而言,它大概是O(n平方),而n是两个单词出现的总和。


-1

我开发的这段代码将解决问题:

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).


2
仅当每个字符串只有一个出现时,它才会起作用
Vic 2014年

-1
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);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.