Bagaimana saya bisa mengganti dua string dengan cara yang satu tidak berakhir menggantikan yang lain?

162

Katakanlah saya memiliki kode berikut:

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

Setelah kode ini berjalan, nilainya storyakan"Once upon a time, there was a foo and a foo."

Masalah serupa terjadi jika saya menggantinya dengan urutan yang berlawanan:

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

Nilai storyakan"Once upon a time, there was a bar and a bar."

Tujuan saya adalah storymenjadi "Once upon a time, there was a bar and a foo."Bagaimana saya bisa mencapai itu?

Pikamander2
sumber
7
+1 pasti ada beberapa fungsi swap(String s1, String s2, String s3)yang menukar semua kejadian s2dengan s3, dan sebaliknya.
Ryan
Bisakah kita mengasumsikan hanya ada satu kemunculan dari setiap kata swappable di input?
icza
14
Kasus sudut: Apa yang kita harapkan sebagai keluaran saat mengganti "ab" dan "ba" dalam "ababababababa"?
Hagen von Eitzen
1
Anda memiliki beberapa solusi bagus di bawah ini, tetapi apakah Anda mengerti mengapa pendekatan Anda tidak berhasil? Pertama, Anda memiliki "ada foo dan bar". Setelah ganti pertama ("foo" -> "bar") Anda memiliki "ada bar dan bar". Anda sekarang memiliki 2 kemunculan "bar", jadi pengganti kedua Anda tidak melakukan apa yang Anda harapkan - ia tidak memiliki cara untuk mengetahui bahwa Anda hanya ingin mengganti yang belum pernah Anda ganti sebelumnya. @HagenvonEitzen Menarik. Saya akan mengharapkan solusi yang berfungsi untuk mencocokkan dan mengganti yang pertama dari kedua string yang ditemukannya dan kemudian ulangi dari akhir bagian yang diganti.
DeveloperInDevelopment
1
Solusi Jeroen adalah salah satu yang sering saya gunakan dalam editor teks, ketika saya perlu melakukan penggantian nama secara massal. Ini sederhana, mudah dimengerti, tidak memerlukan perpustakaan khusus, dan bisa sangat mudah dengan sedikit pemikiran.
Hot Licks

Jawaban:

88

Gunakan replaceEach()metode dari Apache Commons StringUtils :

StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})
Alan Hay
sumber
2
ada yang tahu apa yang benar-benar replaceEach lakukan secara internal?
Marek
3
@Marek sangat mungkin bahwa fungsi melakukan pencarian dan mengindeks setiap item yang ditemukan, kemudian menggantikan semuanya setelah semuanya diindeks.
16
Anda dapat menemukan sumber untuk ini di sini di sekitar baris 4684.
Jeroen Vannevel
Sangat disayangkan bahwa itu adalah no-op ketika nulldisahkan.
rightfold
87

Anda menggunakan nilai perantara (yang belum ada dalam kalimat).

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

Sebagai tanggapan terhadap kritik: jika Anda menggunakan string tidak umum yang cukup besar seperti zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddhodfqsqc, nvùq ^ μù; d & dsq: d:; bahkan di mana tidak ada gunanya, tidak akan terjadi di mana pun. bahwa pengguna akan pernah memasukkan ini. Satu-satunya cara untuk mengetahui apakah pengguna akan adalah dengan mengetahui kode sumber dan pada saat itu Anda dengan tingkat kekhawatiran lainnya.

Ya, mungkin ada cara regex mewah. Saya lebih suka sesuatu yang dapat dibaca yang saya tahu tidak akan pecah pada saya juga.

Juga mengulangi saran luar biasa yang diberikan oleh @David Conrad di komentar :

Jangan gunakan beberapa string secara cerdik (bodoh) yang dipilih untuk menjadi tidak mungkin. Gunakan karakter dari Area Penggunaan Pribadi Unicode, U + E000..U + F8FF. Hapus karakter seperti itu terlebih dahulu, karena mereka seharusnya tidak secara sah berada di input (mereka hanya memiliki arti khusus aplikasi dalam beberapa aplikasi), kemudian gunakan sebagai placeholder saat mengganti.

Jeroen Vannevel
sumber
4
@arshajii Saya kira itu tergantung pada definisi Anda tentang "lebih baik" ... jika itu berfungsi dan dapat diterima, lanjutkan ke tugas pemrograman berikutnya dan memperbaikinya nanti selama refactoring akan menjadi pendekatan saya.
Matt Coubrough
24
Jelas "lala" hanyalah sebuah contoh. Dalam produksi, Anda harus menggunakan " zq515sqdqs5d5sq1dqs4d1q5dqqé" & é & € sdq: d:;) àçàçlala ".
Jeroen Vannevel
81
Jangan gunakan beberapa string secara cerdik (bodoh) yang dipilih untuk menjadi tidak mungkin. Gunakan karakter dari Area Penggunaan Pribadi Unicode, U + E000..U + F8FF. Hapus karakter seperti itu terlebih dahulu, karena mereka seharusnya tidak secara sah berada di input (mereka hanya memiliki arti khusus aplikasi dalam beberapa aplikasi), kemudian gunakan sebagai placeholder saat mengganti.
David Conrad
22
Sebenarnya, setelah membaca FAQ Unicode tentang hal itu , saya pikir karakter nonchar dalam kisaran U + FDD0..U + FDEF akan menjadi pilihan yang lebih baik.
David Conrad
6
@Taemyr Tentu, tetapi seseorang harus membersihkan input, kan? Saya berharap bahwa fungsi penggantian-string bekerja pada semua string, tetapi fungsi ini rusak untuk input yang tidak aman.
Navin
33

Anda dapat mencoba sesuatu seperti ini, menggunakan Matcher#appendReplacementdan 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());
Sekali waktu, ada bar dan foo.
arshajii
sumber
2
Apakah pekerjaan ini jika foo, bardan storysemua memiliki nilai yang tidak diketahui?
Stephen P
1
@StephenP Saya pada dasarnya hard-coded "foo"dan "bar"string pengganti seperti OP dalam kodenya, tetapi jenis pendekatan yang sama akan berfungsi dengan baik bahkan jika nilai-nilai itu tidak diketahui (Anda harus menggunakan if/ else ifbukannya switchdalam while-lompat).
arshajii
6
Anda harus berhati-hati dalam membuat regex. Pattern.quoteakan berguna, atau \Qdan \E.
David Conrad
1
@arshajii - ya, membuktikannya pada diri saya sebagai metode "swapThese" dengan menggunakan word1, word2, dan story sebagai parameter. +1
Stephen P
4
Bahkan lebih bersih akan menggunakan pola (foo)|(bar)dan kemudian memeriksa m.group(1) != null, untuk menghindari mengulangi kata-kata yang cocok.
Jörn Horstmann
32

Ini bukan masalah yang mudah. Dan semakin banyak parameter penggantian pencarian yang Anda miliki, semakin sulit hasilnya. Anda memiliki beberapa opsi, tersebar di palet jelek, elegan, dan boros:

  • Gunakan StringUtils.replaceEachdari Apache Commons seperti yang disarankan @AlanHay . Ini adalah opsi yang baik jika Anda bebas menambahkan dependensi baru di proyek Anda. Anda mungkin beruntung: ketergantungan mungkin sudah termasuk dalam proyek Anda

  • Gunakan pengganti sementara seperti yang disarankan @Jeroen , dan lakukan penggantian dalam 2 langkah:

    1. Ganti semua pola pencarian dengan tag unik yang tidak ada di teks asli
    2. Ganti placeholder dengan pengganti target asli

    Ini bukan pendekatan yang bagus, karena beberapa alasan: perlu memastikan bahwa tag yang digunakan pada langkah pertama benar-benar unik; ia melakukan lebih banyak operasi penggantian string daripada yang benar-benar diperlukan

  • Buat regex dari semua pola dan gunakan metode dengan MatcherdanStringBuffer seperti yang disarankan oleh @arshajii . Ini tidak buruk, tetapi tidak terlalu bagus, karena membangun regex adalah jenis peretasan, dan itu melibatkan StringBufferyang keluar dari mode beberapa waktu lalu yang mendukung StringBuilder.

  • Gunakan solusi rekursif yang diusulkan oleh @mjolka , dengan memecah string pada pola yang cocok, dan berulang pada segmen yang tersisa. Ini adalah solusi yang bagus, ringkas dan cukup elegan. Kelemahannya adalah berpotensi banyak operasi substring dan gabungan, dan batas ukuran tumpukan yang berlaku untuk semua solusi rekursif

  • Membagi teks menjadi kata-kata dan menggunakan stream Java 8 untuk melakukan penggantian secara elegan seperti yang disarankan @msandiford , tetapi tentu saja itu hanya berfungsi jika Anda setuju dengan pemisahan pada batas kata, yang membuatnya tidak cocok sebagai solusi umum

Ini versi saya, berdasarkan ide yang dipinjam dari implementasi Apache . Ini tidak sederhana atau elegan, tetapi berfungsi, dan harus relatif efisien, tanpa langkah yang tidak perlu. Singkatnya, ini berfungsi seperti ini: berulang kali menemukan pola pencarian yang cocok berikutnya dalam teks, dan gunakan a StringBuilderuntuk mengakumulasi segmen yang tak tertandingi dan penggantian.

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

Tes unit:

@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"})
    );
}
janos
sumber
21

Cari kata pertama yang akan diganti. Jika ada di string, kembalilah pada bagian string sebelum terjadinya, dan pada bagian string setelah terjadinya.

Jika tidak, lanjutkan dengan kata berikutnya yang akan diganti.

Implementasi yang naif mungkin terlihat seperti ini

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

Penggunaan sampel:

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

Keluaran:

Once upon a foo, there was a bar and a baz.

Versi yang kurang naif:

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

Sayangnya, Java Stringtidak memiliki indexOf(String str, int fromIndex, int toIndex)metode. Saya telah menghilangkan implementasi di indexOfsini karena saya tidak yakin itu benar, tetapi dapat ditemukan di ideone , bersama dengan beberapa timing kasar dari berbagai solusi yang diposting di sini.

mjolka
sumber
2
Meskipun menggunakan perpustakaan yang ada seperti apache commons untuk hal-hal seperti ini tidak diragukan lagi cara termudah untuk menyelesaikan masalah yang cukup umum ini, Anda telah menunjukkan implementasi yang bekerja pada bagian kata-kata, kata-kata yang diputuskan dalam runtime dan tanpa mengganti substring dengan token ajaib tidak seperti (saat ini) jawaban dengan suara lebih tinggi. +1
Buhb
Cantik, tetapi menyentuh tanah ketika file input 100 mb disediakan.
Christophe De Troyer
12

One-liner di 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());
Vitalii Fedorenko
sumber
11

Berikut adalah kemungkinan streaming Java 8 yang mungkin menarik bagi sebagian orang:

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

Berikut ini adalah perkiraan algoritma yang sama di 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);
clstrfsck
sumber
10
Ini adalah saran yang bagus ketika hal-hal yang ingin Anda ganti adalah kata - kata aktual yang dipisahkan oleh spasi (atau serupa), tetapi ini tidak akan berfungsi untuk mengganti substring kata.
Simon Forsberg
+1 untuk aliran Java8. Sayang sekali ini membutuhkan pembatas.
Navin
6

Jika Anda ingin mengganti kata dalam kalimat yang dipisahkan oleh spasi seperti yang ditunjukkan pada contoh Anda, Anda dapat menggunakan algoritma sederhana ini.

  1. Pisahkan cerita di ruang putih
  2. Ganti setiap elemen, jika foo menggantinya ke bilah dan sebaliknya varsa
  3. Bergabung kembali dengan array menjadi satu string

Jika pemisahan pada ruang tidak dapat diterima, seseorang dapat mengikuti algoritma alternatif ini. Anda harus menggunakan string yang lebih panjang terlebih dahulu. Jika string foo dan bodoh, Anda harus menggunakan bodoh dulu dan kemudian foo.

  1. Pisahkan kata foo
  2. Ganti bilah dengan foo setiap elemen array
  3. Gabungkan array itu kembali menambahkan bar setelah setiap elemen kecuali yang terakhir
fastcodejava
sumber
1
Ini adalah apa yang saya pikirkan untuk menyarankan juga. Padahal itu menambah batasan bahwa teks adalah kata-kata yang dikelilingi oleh spasi. :)
Pengembang Marius Žilėnas
@ MariusŽilėnas Saya telah menambahkan algoritma alternatif.
fastcodejava
5

Inilah jawaban yang tidak terlalu rumit menggunakan Peta.

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

Dan metode disebut

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

Outputnya adalah: luar biasa adalah Raffy, Raffy Raffy mengagumkan mengagumkan

WillingLearner
sumber
1
berlari replaced.replaceAll("Raffy", "Barney");setelah ini akan membuatnya menjadi legen ... tunggu; Dary !!!
Keale
3

Jika Anda ingin dapat menangani beberapa kemunculan string pencarian yang akan diganti, Anda dapat melakukannya dengan mudah dengan memisahkan string pada setiap istilah pencarian, lalu menggantinya. Berikut ini sebuah contoh:

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;
}
ventsyv
sumber
3

Anda dapat mencapai tujuan Anda dengan blok kode berikut:

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

Itu menggantikan kata-kata terlepas dari urutannya. Anda dapat memperluas prinsip ini menjadi metode utilitas, seperti:

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

Yang akan dikonsumsi sebagai:

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" }));
Leonardo Braga
sumber
3

Ini berfungsi dan sederhana:

public String replaceBoth(String text, String token1, String token2) {            
    return text.replace(token1, "\ufdd0").replace(token2, token1).replace("\ufdd0", token2);
    }

Anda menggunakannya seperti ini:

replaceBoth("Once upon a time, there was a foo and a bar.", "foo", "bar");

Catatan: ini bergantung pada Strings yang tidak mengandung karakter \ufdd0, yang merupakan karakter yang secara permanen disediakan untuk penggunaan internal oleh Unicode (Lihat http://www.unicode.org/faq/private_use.html ):

Saya pikir itu tidak perlu, tetapi jika Anda ingin benar-benar aman, Anda dapat menggunakan:

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);
    }
MarcG
sumber
3

Bertukar Hanya Satu Kejadian

Jika hanya ada satu kejadian dari masing-masing string yang dapat ditukar di input, Anda dapat melakukan hal berikut:

Sebelum melanjutkan ke penggantian apa pun, dapatkan indeks kemunculan kata-kata tersebut. Setelah itu kami hanya mengganti kata yang ditemukan di indeks ini, dan tidak semua kejadian. Solusi ini menggunakan StringBuilderdan tidak menghasilkan Stringseperti perantara String.replace().

Satu hal yang perlu diperhatikan: jika kata swapable memiliki panjang yang berbeda, setelah ganti pertama indeks kedua mungkin berubah (jika kata pertama muncul sebelum kata kedua) tepat dengan perbedaan 2 panjang. Jadi, menyelaraskan indeks kedua akan memastikan ini berfungsi bahkan jika kita bertukar kata dengan panjang yang berbeda.

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

Bertukar Jumlah Kejadian Sewenang-wenang

Analog dengan kasus sebelumnya, pertama-tama kita akan mengumpulkan indeks (kemunculan) kata-kata, tetapi dalam kasus ini akan menjadi daftar bilangan bulat untuk setiap kata, bukan hanya satu int. Untuk ini, kami akan menggunakan metode utilitas berikut:

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

Dan menggunakan ini kita akan mengganti kata-kata dengan yang lain dengan mengurangi indeks (yang mungkin perlu berganti-ganti antara 2 kata swapable) sehingga kita bahkan tidak perlu memperbaiki indeks setelah diganti:

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();
}
icza
sumber
Saya tidak yakin bagaimana java menangani unicode, tetapi padanan C # dari kode ini akan salah. Masalahnya adalah bahwa substring yang indexOfcocok mungkin tidak memiliki panjang yang sama dengan searchstring berkat keistimewaan kesetaraan string unicode.
CodesInChaos
@CodesInChaos Ia berfungsi dengan sempurna di Java karena Java Stringadalah array karakter dan bukan array byte. Semua metode Stringdan StringBuilderberoperasi pada karakter bukan pada byte, yang "bebas encoding". Dengan demikian indexOfkecocokan memiliki panjang (karakter) yang persis sama dengan string pencarian.
icza
Baik dalam C # dan java string adalah urutan unit kode UTF-16. Masalahnya adalah bahwa ada urutan berbeda dari codepoint yang dianggap unicode setara. Misalnya ädapat dikodekan sebagai satu codepoint atau sebagai yang adiikuti oleh penggabungan ¨. Ada juga beberapa codepoint yang diabaikan, seperti zero-width (non) joiners. Tidak masalah jika string terdiri dari byte, karakter atau apa pun, tetapi aturan perbandingan mana yang indexOfdigunakan. Ini mungkin hanya menggunakan unit kode dengan perbandingan unit kode ("Ordinal") atau mungkin menerapkan kesetaraan unicode. Saya tidak tahu mana yang dipilih java.
CodesInChaos
Misalnya "ab\u00ADc".IndexOf("bc")kembali 1dalam .net yang cocok dengan string dua karakter bcke string tiga karakter.
CodesInChaos
1
@CodesInChaos Saya mengerti maksud Anda sekarang. Di Jawa "ab\u00ADc".indexOf("bc")pengembalian -1yang berarti "bc"tidak ditemukan di "ab\u00ADc". Jadi tetap ada bahwa di Jawa algoritma di atas berfungsi, indexOf()kecocokan memiliki panjang (karakter) yang sama persis dengan string pencarian, dan indexOf()hanya melaporkan kecocokan jika charsequences (codepoints) cocok.
icza
2

Sangat mudah untuk menulis metode untuk melakukan ini menggunakan 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();
}

Pengujian:

String s = "There are three cats and two dogs.";
s = simultaneousReplace(s,
    "cats", "dogs",
    "dogs", "budgies");
System.out.println(s);

Keluaran:

Ada tiga anjing dan dua sobat.

Tidak segera jelas, tetapi fungsi seperti ini masih bisa bergantung pada urutan penggantian yang ditentukan. Mempertimbangkan:

String truth = "Java is to JavaScript";
truth += " as " + simultaneousReplace(truth,
    "JavaScript", "Hamster",
    "Java", "Ham");
System.out.println(truth);

Keluaran:

Java untuk JavaScript seperti Ham adalah untuk Hamster

Tetapi balikkan penggantian:

truth += " as " + simultaneousReplace(truth,
    "Java", "Ham",
    "JavaScript", "Hamster");

Keluaran:

Java untuk JavaScript seperti Ham adalah untuk HamScript

Ups! :)

Oleh karena itu kadang-kadang berguna untuk memastikan mencari kecocokan terpanjang (seperti strtrfungsi PHP , misalnya). Versi metode ini akan melakukan itu:

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

Perhatikan bahwa metode di atas peka terhadap huruf besar-kecil. Jika Anda memerlukan versi case-insensitive, mudah untuk memodifikasi yang di atas karena String.regionMatchesdapat mengambil ignoreCaseparameter.

Boann
sumber
2

Jika Anda tidak menginginkan dependensi, Anda cukup menggunakan array yang memungkinkan perubahan satu kali saja. Ini bukan solusi yang paling efisien, tetapi harus bekerja.

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

Lalu, itu akan berhasil.

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.
Dermaga-Alexandre Bouchard
sumber
2

Anda sedang melakukan beberapa operasi pencarian-ganti pada input. Ini akan menghasilkan hasil yang tidak diinginkan ketika string pengganti berisi string pencarian. Pertimbangkan bar foo->, contoh bar-foo, berikut adalah hasil untuk setiap iterasi:

  1. Sekali waktu, ada foo dan bar. (memasukkan)
  2. Sekali waktu, ada bar dan bar. (foo-> bar)
  3. Sekali waktu, ada foo dan foo. (bar-> foo, keluaran)

Anda perlu melakukan penggantian dalam satu iterasi tanpa kembali. Solusi brute-force adalah sebagai berikut:

  1. Cari input dari posisi saat ini hingga akhir untuk beberapa string pencarian hingga ditemukan kecocokan
  2. Ganti string pencarian yang cocok dengan string pengganti yang sesuai
  3. Atur posisi saat ini ke karakter berikutnya setelah string yang diganti
  4. Ulang

Fungsi seperti String.indexOfAny(String[]) -> int[]{index, whichString}itu akan berguna. Berikut ini sebuah contoh (bukan yang paling efisien):

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

Beberapa tes:

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 di IDEONE
Demo di IDEONE, kode alternatif

Salman A
sumber
1

Anda selalu bisa menggantinya dengan kata yang Anda yakin akan muncul di tempat lain di string, dan kemudian lakukan penggantian kedua nanti:

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

Perhatikan bahwa ini tidak akan berfungsi jika "StringYouAreSureWillNeverOccur"terjadi.

Pokechu22
sumber
5
Gunakan karakter dari Area Penggunaan Pribadi Unicode, U + E000..U + F8FF, membuat StringThatCannotEverOccur. Anda dapat memfilternya terlebih dahulu karena seharusnya tidak ada di input.
David Conrad
Atau U + FDD0..U + FDEF, "Noncharacters", yang disediakan untuk penggunaan internal.
David Conrad
1

Pertimbangkan untuk menggunakan StringBuilder

Kemudian simpan indeks di mana setiap string harus dimulai. Jika Anda menggunakan karakter tempat penampung di setiap posisi, lalu hapus, dan masukkan string pengguna. Anda kemudian dapat memetakan posisi ujung dengan menambahkan panjang string ke posisi awal.

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;
Imheroldman
sumber
1

Yang hanya bisa saya bagikan adalah metode saya sendiri.

Anda dapat menggunakan sementara String temp = "<?>";atauString.Format();

Ini adalah contoh kode saya yang dibuat di aplikasi konsol via - "Hanya Ide, Tidak Tepat Jawaban" .

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

Atau Anda juga bisa menggunakan 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;
        }

Keluaran: time upon a Once, there was a bar and a foo.

Leonel Sarmiento
sumber
Ini cukup rapi. Apa yang akan Anda lakukan jika dia ingin mengganti "_"?
Pier-Alexandre Bouchard
@ Pier-AlexandreBouchard Dalam metode saya mengubah nilai tempdari "_"menjadi <?>. Tetapi jika diperlukan, apa yang bisa dia lakukan adalah menambahkan parameter lain ke metode yang akan mengubah temp. - "Lebih baik untuk membuatnya tetap sederhana, kan?"
Leonel Sarmiento
Maksud saya adalah bahwa Anda tidak dapat menjamin hasil yang diharapkan karena jika temp == ganti, cara Anda tidak akan berfungsi.
Pier-Alexandre Bouchard
1

Inilah versi saya, yang berbasis kata:

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

    }

}
Khaled.K
sumber
1
String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."

Cara yang sedikit rumit tetapi Anda perlu melakukan beberapa pemeriksaan lagi.

1.mengonversi string ke array karakter

   String temp[] = story.split(" ");//assume there is only spaces.

2.loop on temp dan ganti foodengan bardan bardengan fookarena tidak ada peluang mendapatkan string yang dapat diganti lagi.

Key_coder
sumber
1

Nah, jawaban yang lebih pendek adalah ...

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);
Elvis Lima
sumber
1

Dengan menggunakan jawaban yang ditemukan di sini Anda dapat menemukan semua kemunculan string yang ingin Anda ganti.

Jadi misalnya Anda menjalankan kode pada jawaban SO di atas. Buat dua tabel indeks (misalkan bilah dan foo tidak hanya muncul sekali di string Anda) dan Anda bisa bekerja dengan tabel-tabel itu untuk menggantinya di string Anda.

Sekarang untuk mengganti lokasi indeks tertentu yang dapat Anda gunakan:

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

Sedangkan posindeks di mana string Anda mulai (dari tabel indeks yang saya kutip di atas). Jadi katakanlah Anda membuat dua tabel indeks untuk masing-masing. Mari kita panggil mereka indexBardan indexFoo.

Sekarang, sebagai gantinya, Anda dapat menjalankan dua loop, satu untuk setiap penggantian yang ingin Anda buat.

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

Demikian pula loop lain untuk indexFoo.

Ini mungkin tidak seefisien jawaban lain di sini, tetapi lebih mudah dipahami daripada Peta atau hal lainnya.

Ini akan selalu memberi Anda hasil yang Anda inginkan dan untuk beberapa kemungkinan kemunculan setiap string. Selama Anda menyimpan indeks dari setiap kejadian.

Juga jawaban ini tidak memerlukan rekursi atau ketergantungan eksternal. Sejauh kerumitannya berjalan dengan baik adalah O (n kuadrat), sedangkan n adalah jumlah dari kedua kata tersebut.

John Demetriou
sumber
-1

Saya mengembangkan kode ini akan memecahkan masalah:

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

Dalam penggunaan utama change(story,word2,word1).

Onur
sumber
2
Ini hanya akan berfungsi jika hanya ada satu penampilan dari setiap string
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);
Amir Saniyan
sumber