片方がもう片方を置き換えないように2つの文字列を置き換えるにはどうすればよいですか?


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)すべての出現をs2と交換する関数が必ずあるはずs3です。逆もまた同様です。
ライアン、

入力内のスワップ可能な各単語が1つだけ存在すると想定できますか?
icza 2014年

14
特殊なケース:「ababababababa」で「ab」と「ba」を入れ替えると、出力として何が期待されますか?
Hagen von Eitzen 2014年

1
以下にいくつかの良い解決策がありますが、あなたのアプローチがうまくいかなかった理由を理解していますか?まず、「fooとbarがありました」です。最初の置換( "foo"-> "bar")の後は、「バーとバーがあった」ということになります。これで「bar」が2回出現するため、2回目の置換では期待どおりに動作しません-前回取り替えなかったものだけを取り替えたいということを知る方法はありません。@HagenvonEitzen興味深い。私は、有効なソリューションが最初に見つかったいずれかの文字列の最初と一致して置き換え、次に置き換えられたセクションの最後から繰り返すことが期待されます。
DeveloperInDevelopment

1
Jeroenのソリューションは、テキストエディターで頻繁に使用するソリューションであり、一括で名前を変更する必要があります。シンプルで理解しやすく、特別なライブラリを必要とせず、少し考えれば簡単です。
ホットリックス2014年

回答:


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:;)àçàçlalaを指し、それを使用する場合でも、zq515sqdqs5d5sq1dqs4d1q5dqqé"&é5d4sqjshsjddjhodfqsqcユーザーがこれを入力することはありません。ユーザーが入力するかどうかを確認する唯一の方法は、ソースコードを知ることであり、その時点で他の完全なレベルの心配を抱えています。

はい、たぶん豪華な正規表現の方法があります。私は、私にも発生しないことがわかっている読みやすいものを好みます。

コメントで@David Conradによって与えられた優れたアドバイスを繰り返します

ありそうにないように巧妙に(愚かに)選ばれた文字列を使用しないでください。Unicode Private Use Area、U + E000..U + F8FFの文字を使用します。そのような文字は最初に削除します。それらは入力に正当に含まれるべきではないため(一部のアプリケーションではアプリケーション固有の意味しか持たないため)、置換するときにプレースホルダーとして使用します。


4
@arshajiiそれはあなたの「より良い」の定義に依存すると思います...それが機能し、許容できるパフォーマンスである場合、次のプログラミングタスクに進み、リファクタリング中に後でそれを改善することが私のアプローチでしょう。
Matt Coubrough 2014年

24
明らかに「ララ」はほんの一例です。生産では、 "使用する必要があります)àçàçlala;:D:&E&€SDQzq515sqdqs5d5sq1dqs4d1q5dqqé"。」
イェルーンVannevel

81
ありそうにないように巧妙に(愚かに)選ばれた文字列を使用しないでください。Unicode Private Use Area、U + E000..U + F8FFの文字を使用します。そのような文字は最初に削除してください。それらは入力に正当に含まれるべきではないため(一部のアプリケーションではアプリケーション固有の意味しかないため)、置換するときにプレースホルダーとして使用します。
David Conrad、

22
実際、Unicode FAQを読んだ後、U + FDD0..U + FDEFの範囲の非文字の方がより良い選択だと思います。
デビッドコンラッド

6
@Taemyrもちろんですが、誰かが入力を無害化する必要がありますよね?文字列置換関数はすべての文字列で機能すると思いますが、この関数は安全でない入力のために機能しません。
Navin、2014年

33

Matcher#appendReplacementand を使用して、このようなことを試すことができます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());
むかしむかし、バーとフーがいました。

2
、、およびすべてに不明な値がある場合foo、これは機能しますか?barstory
Stephen P

1
@StephenP私は基本的にハードコーディングされてきた"foo""bar"OPは彼のコードであったが、アプローチの同じタイプは、これらの値は、あなたが使用する必要があるだろう(知られていない場合であっても正常に動作します置換文字列としてif/ else ifの代わりに、switchwhile-ループ)。
arshajii 14年

6
正規表現の作成には注意が必要です。Pattern.quote便利になる、またはだろう\Q\E
David Conrad

1
@arshajii-うん、word1、word2、およびストーリーをパラメーターとして取る「swapThese」メソッドとしてそれを私に証明しました。+1
スティーブンP

4
さらにきれいにするには、パターンを使用して(foo)|(bar)から照合しm.group(1) != null、一致する単語を繰り返さないようにします。
ジョーンHorstmann

32

これは簡単な問題ではありません。そして、より多くの検索置換パラメーターを使用するほど、取得するトリッキーなものになります。醜い上品で効率的な無駄のパレットに散らばっているいくつかのオプションがあります:

  • @AlanHayを推奨するようにStringUtils.replaceEach、Apache Commonsから使用します。これは、プロジェクトに新しい依存関係を自由に追加できる場合に適したオプションです。あなたは幸運になるかもしれません:依存関係はすでにプロジェクトに含まれているかもしれません

  • @Jeroenが提案する一時的なプレースホルダーを使用し、2つのステップで置換を実行します。

    1. すべての検索パターンを、元のテキストには存在しない一意のタグに置き換えます
    2. プレースホルダーを実際のターゲット置換に置き換えます

    これは、いくつかの理由で優れたアプローチではありません。最初のステップで使用されるタグが本当に一意であることを確認する必要があるためです。本当に必要以上の文字列置換操作を実行する

  • すべてのパターンから正規表現を構築してメソッドを使用MatcherしてStringBufferによって示唆されているように@arshajii。これはひどいことではありませんが、それほど素晴らしいことではありません。正規表現を作成することは一種のハックであり、これは、以前に時代遅れになったStringBufferものに賛成しているためですStringBuilder

  • 一致したパターンで文字列を分割し、残りのセグメントで再帰することにより、@mjolkaによって提案された再帰的ソリューションを使用します。これはすばらしい解決策であり、コンパクトで非常にエレガントです。その弱点は、潜在的に多くの部分文字列と連結演算、およびすべての再帰的ソリューションに適用されるスタックサイズの制限です。

  • @msandifordが示唆するように、テキストを単語に分割し、Java 8ストリームを使用して置換をエレガントに実行しますが、もちろん、単語の境界で分割しても問題がないため、一般的な解決策としては適していません。

これは、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;
    }
}

単体テスト:

@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にStringindexOf(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());
  • ルックアラウンド正規表現(?<=?=):http : //www.regular-expressions.info/lookaround.html
  • 単語に特殊な正規表現文字を含めることができる場合は、Pattern.quoteを使用してエスケープします。
  • 私は簡潔にするためにguava ImmutableMapを使用していますが、他のどのMapも同様に機能します。

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
これは、置き換えたいものがスペースで区切られた実際の単語(または類似の単語)である場合に役立ちますが、単語の部分文字列を置き換える場合には機能しません。
Simon Forsberg、2014年

Java8ストリームの場合は+1。残念ながら、これには区切り文字が必要です。
Navin、2014年

6

例に示すように、空白で区切られた文の単語を置き換える場合は、この単純なアルゴリズムを使用できます。

  1. 空白でストーリーを分割
  2. 各要素を置き換えます。fooの場合はそれをbarに置き換え、逆の場合はvarsaに置き換えます。
  3. 配列を結合して1つの文字列に戻す

スペースでの分割が許容できない場合は、この代替アルゴリズムに従うことができます。最初に長い文字列を使用する必要があります。文字列がfooとfoolの場合、最初にfoolを使用し、次にfooを使用する必要があります。

  1. 単語fooで分割
  2. 配列の各要素をfooに置き換えます
  3. 最後の要素を除く各要素の後にバーを追加してその配列を結合

1
これも私が提案しようと思っていたものです。ただし、テキストがスペースで囲まれた単語であるという制限が追加されます。:)
開発者MariusŽilėnas2014年

@MariusŽilėnas代替アルゴリズムを追加しました。
fastcodejava '11 / 11/14

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

出力は次のとおりです。すごいRaffy、Raffy Raffyはすごい


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

注:これ\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

1つのオカレンスのみをスワップする

入力内の交換可能な文字列がそれぞれ1つだけある場合は、次の操作を実行できます。

置換に進む前に、単語の出現のインデックスを取得します。その後、すべての出現箇所ではなく、これらのインデックスで見つかった単語のみを置き換えます。このソリューションが使用するStringBuilderと、中間生成されないStringようなのをString.replace()

注意すべき点の1つ:交換可能な単語の長さが異なる場合、最初の置換後、2番目のインデックスは2つの長さの違いで正確に変わる可能性があります(最初の単語が2番目の単語の前にある場合)。したがって、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();
}

任意の発生回数の入れ替え

前のケースと同様に、最初に単語のインデックス(オカレンス)を収集しますが、この場合は1つだけではなく、各単語の整数のリストになります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コード単位のシーケンスです。問題は、ユニコードが同等と見なすコードポイントの異なるシーケンスがあることです。たとえばä、単一のコードポイントとしてエンコードしたり、aその後に組み合わせてエンコードしたりできます¨。ゼロ幅(非)ジョイナーなど、無視されるコードポイントもあります。文字列がバイト、文字、その他のどれで構成されているかは関係ありませんが、どの比較ルールがindexOf使用されているかは関係ありません。コード単位の比較( "序数")による単純なコード単位を使用する場合や、Unicodeの同等性を実装する場合があります。どのJavaを選択したかわかりません。
CodesInChaos 2014年

たとえば、2文字の文字列を3文字の文字列に一致させる.netを"ab\u00ADc".IndexOf("bc")返します。1bc
CodesInChaos 2014年

1
@CodesInChaos私はあなたが今何を言っているのか分かります。Javaではを"ab\u00ADc".indexOf("bc")返しますが、-1これは"bc"で見つかりませんでした"ab\u00ADc"。したがって、Javaでは上記のアルゴリズムが機能し、 indexOf()一致の長さは検索文字列とまったく同じ(文字)であり、文字indexOf()シーケンス(コードポイント)が一致する場合にのみ一致が報告されます。
icza 2014年

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

出力:

3匹の犬と2匹の犬がいます。

すぐにはわかりませんが、このような関数は、置換が指定された順序に依存する可能性があります。考慮してください:

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.regionMatchesignoreCaseパラメータを取ることができるため、上記を簡単に変更できます。


2

依存関係が必要ない場合は、1回限りの変更のみを許可する配列を使用できます。これは最も効率的なソリューションではありませんが、機能するはずです。

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. むかしむかし、フーとバーがありました。(入力)
  2. むかしむかし、バーとバーがありました。(foo-> bar)
  3. むかしむかし、フーとフーがいました。(bar-> foo、出力)

戻ることなく、1回の反復で置換を実行する必要があります。ブルートフォースソリューションは次のとおりです。

  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

いつでもそれを文字列のどこにも表示されないことが確実な単語に置き換えて、後で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"発生した場合、これは正しく機能しないことに注意してください。


5
Unicode Private Use Area、U + E000..U + F8FFの文字を使用して、StringThatCannotEverOccurを作成します。それらは入力に存在してはならないので、事前に除外できます。
David Conrad

または、U + FDD0..U + FDEF、「非文字」、これは内部使用のために予約されています。
David Conrad

1

StringBuilderの使用を検討する

次に、各文字列が始まるインデックスを保存します。各位置でプレースホルダー文字を使用している場合は、それを削除して、users文字列を挿入します。次に、文字列の長さを開始位置に追加して、終了位置をマッピングできます。

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.


それはかなりハックです。「_」を置き換えたい場合はどうしますか?
Pier-Alexandre Bouchard 2014年

方法では桟橋-AlexandreBouchard @私はの値を変更tempから"_"にし<?>。ただし、必要に応じて、温度を変更するメソッドに別のパラメーターを追加することができます。-「シンプルなままにしておく方がいいですか?」
Leonel Sarmiento 2014年

私のポイントは、temp == replaceの場合、あなたの方法が機能しないため、yonは期待される結果を保証できないということです。
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の回答でコードを実行します。インデックスの2つのテーブルを作成し(たとえば、barとfooが文字列に一度だけ表示されないようにします)、これらのテーブルを使用して、文字列内のテーブルを置き換えることができます。

ここで、特定のインデックスの場所を置き換えるために使用できます:

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

一方pos、文字列が始まるインデックスは(上記で引用したインデックステーブルから)です。それでは、インデックスごとに2つのテーブルを作成したとします。それらindexBarを呼び出しましょうindexFoo

それらを置き換える際に、2つのループを実行するだけで済みます。

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

同様に別のループ indexFoo

これは他の回答ほど効率的ではないかもしれませんが、マップやその他のものよりも理解するのは簡単です。

これにより、常に希望する結果が得られ、各文字列が複数出現する可能性があります。各発生のインデックスを保存する限り。

また、この回答は、再帰や外部依存関係を必要としません。複雑さに関する限り、それはおそらくO(nの2乗)ですが、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
各ストリングの外観が1つだけある場合にのみ機能します
Vic

-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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.