Làm thế nào tôi có thể thay thế hai chuỗi theo cách mà một chuỗi không kết thúc thay thế chuỗi khác?


162

Hãy nói rằng tôi có mã sau đây:

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

Sau khi mã này chạy, giá trị của storysẽ là"Once upon a time, there was a foo and a foo."

Một vấn đề tương tự xảy ra nếu tôi thay thế chúng theo thứ tự ngược lại:

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

Giá trị của storysẽ là"Once upon a time, there was a bar and a bar."

Mục tiêu của tôi là biến storythành "Once upon a time, there was a bar and a foo."Làm thế nào tôi có thể hoàn thành nó?


7
+1 chắc chắn phải có một số chức năng swap(String s1, String s2, String s3)hoán đổi tất cả các lần xuất hiện s2với s3và ngược lại.
Ryan

Chúng ta có thể giả sử chỉ có một lần xuất hiện của mỗi từ có thể hoán đổi trong đầu vào không?
icza

14
Trường hợp góc: Chúng ta mong đợi gì ở đầu ra khi hoán đổi "ab" và "ba" trong "ababababababa"?
Hagen von Eitzen

1
Bạn có một số giải pháp tốt dưới đây, nhưng bạn có hiểu tại sao phương pháp của bạn không hiệu quả? Đầu tiên, bạn có "đã có một foo và một quán bar". Sau lần thay thế đầu tiên ("foo" -> "bar"), bạn có "có một thanh và một thanh". Bây giờ bạn có 2 lần xuất hiện của "thanh", vì vậy lần thay thế thứ hai của bạn không làm như bạn mong đợi - không có cách nào để biết bạn chỉ muốn thay thế lần xuất hiện mà bạn không thay thế lần trước. @HagenvonEitzen Thú vị. Tôi mong đợi một giải pháp làm việc phù hợp và thay thế chuỗi đầu tiên của chuỗi mà nó tìm thấy và sau đó lặp lại từ cuối phần được thay thế.
DeveloperInDevelopment

1
Giải pháp của Jeroen là giải pháp tôi thường sử dụng trong các trình soạn thảo văn bản, khi tôi cần đổi tên hàng loạt. Nó đơn giản, dễ hiểu, không yêu cầu thư viện đặc biệt và có thể dễ dàng hiểu được với một chút suy nghĩ.
Hot Licks

Câu trả lời:


88

Sử dụng replaceEach()phương thức từ Apache Commons StringUtils :

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

2
bất kỳ ý tưởng chính xác những gì thay thếEach làm trong nội bộ?
Marek

3
@Marek rất có khả năng chức năng thực hiện tìm kiếm và lập chỉ mục cho từng mục được tìm thấy, sau đó thay thế tất cả chúng một khi tất cả chúng đã được lập chỉ mục.

16
Bạn có thể tìm thấy nguồn cho điều này ở đây xung quanh dòng 4684.
Jeroen Vannevel

Thật đáng tiếc rằng nó là một no-op khi nullđược thông qua, mặc dù.
đúng vào

87

Bạn sử dụng một giá trị trung gian (chưa có trong câu).

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

Như một phản ứng với những lời chỉ trích: nếu bạn sử dụng một lực lượng đủ chuỗi hiếm lớn như zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddjhodfqsqc, nvùq ^ μù; d & € sdq: d:;) àçàçlala và sử dụng đó, nó dường như không điểm mà tôi thậm chí sẽ không tranh luận nó người dùng sẽ nhập thông tin này. Cách duy nhất để biết liệu người dùng có biết hay không là bằng cách biết mã nguồn và tại thời điểm đó, bạn có một mức độ lo lắng khác.

Vâng, có thể có những cách regex ưa thích. Tôi thích thứ gì đó có thể đọc được mà tôi biết cũng sẽ không xảy ra với tôi.

Cũng nhắc lại lời khuyên tuyệt vời được đưa ra bởi @David Conrad trong các bình luận :

Đừng sử dụng một số chuỗi thông minh (ngu ngốc) được chọn là không thể. Sử dụng các ký tự từ Vùng sử dụng riêng Unicode, U + E000..U + F8FF. Trước tiên, xóa bất kỳ ký tự nào như vậy, vì chúng không nên có trong đầu vào một cách hợp pháp (chúng chỉ có ý nghĩa cụ thể của ứng dụng trong một số ứng dụng), sau đó sử dụng chúng làm trình giữ chỗ khi thay thế.


4
@arshajii Tôi đoán điều đó phụ thuộc vào định nghĩa của bạn về "tốt hơn" ... nếu nó hoạt động và có hiệu quả chấp nhận được, chuyển sang nhiệm vụ lập trình tiếp theo và cải thiện nó sau này trong quá trình tái cấu trúc sẽ là cách tiếp cận của tôi.
Matt Coubrough

24
Rõ ràng "lala" chỉ là một ví dụ. Trong sản xuất, bạn nên sử dụng " zq515sqdqs5d5sq1dqs4d1q5dqqé" & é & € sdq: d :;) àçàçlala ".
Jeroen Vannevel

81
Đừng sử dụng một số chuỗi thông minh (ngu ngốc) được chọn là không thể. Sử dụng các ký tự từ Vùng sử dụng riêng Unicode, U + E000..U + F8FF. Trước tiên, xóa bất kỳ ký tự nào như vậy, vì chúng không nên có trong đầu vào một cách hợp pháp (chúng chỉ có ý nghĩa cụ thể của ứng dụng trong một số ứng dụng), sau đó sử dụng chúng làm trình giữ chỗ khi thay thế.
David Conrad

22
Trên thực tế, sau khi đọc Câu hỏi thường gặp về Unicode , tôi nghĩ rằng các ký tự không trong phạm vi U + FDD0..U + FDEF sẽ là lựa chọn tốt hơn nữa.
David Conrad

6
@Taemyr Chắc chắn, nhưng ai đó phải vệ sinh đầu vào, phải không? Tôi hy vọng rằng một chức năng thay thế chuỗi hoạt động trên tất cả các chuỗi, nhưng chức năng này bị phá vỡ cho các đầu vào không an toàn.
Navin

33

Bạn có thể thử một cái gì đó như thế này, sử dụng Matcher#appendReplacementMatcher#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());
Ngày xửa ngày xưa, có một quán bar và một foo.

2
Làm việc này nếu foo, barstorytất cả đều có giá trị chưa biết?
Stephen P

1
@StephenP Về cơ bản, tôi đã mã hóa các chuỗi thay thế "foo""bar"chuỗi thay thế như OP có trong mã của mình, nhưng cách tiếp cận tương tự sẽ hoạt động tốt ngay cả khi các giá trị đó không được biết (bạn phải sử dụng if/ else ifthay vì switchtrong while-vòng).
arshajii

6
Bạn phải cẩn thận trong việc tạo regex. Pattern.quotesẽ có ích, hay \Q\E.
David Conrad

1
@arshajii - vâng, đã chứng minh điều đó với bản thân mình như là một phương thức "exchangeThese" lấy word1, word2 và câu chuyện làm tham số. +1
Stephen P

4
Thậm chí sạch hơn sẽ là sử dụng mẫu (foo)|(bar)và sau đó kiểm tra lại m.group(1) != null, để tránh lặp lại các từ cho khớp.
Jorn Horstmann

32

Đây không phải là một vấn đề dễ dàng. Và bạn càng có nhiều tham số thay thế tìm kiếm, nó càng phức tạp hơn. Bạn có một số tùy chọn, nằm rải rác trên bảng màu của sự xấu xí thanh lịch, lãng phí hiệu quả:

  • Sử dụng StringUtils.replaceEachtừ Apache Commons như khuyến nghị @AlanHay . Đây là một lựa chọn tốt nếu bạn thoải mái thêm các phụ thuộc mới vào dự án của mình. Bạn có thể gặp may mắn: sự phụ thuộc có thể đã được bao gồm trong dự án của bạn

  • Sử dụng trình giữ chỗ tạm thời như @Jeroen đề xuất và thực hiện thay thế theo 2 bước:

    1. Thay thế tất cả các mẫu tìm kiếm bằng một thẻ duy nhất không tồn tại trong văn bản gốc
    2. Thay thế trình giữ chỗ bằng thay thế mục tiêu thực sự

    Đây không phải là một cách tiếp cận tuyệt vời, vì nhiều lý do: nó cần đảm bảo rằng các thẻ được sử dụng trong bước đầu tiên thực sự độc đáo; nó thực hiện nhiều hoạt động thay thế chuỗi hơn thực sự cần thiết

  • Xây dựng một regex từ tất cả các mẫu và sử dụng phương thức với MatcherStringBuffer theo đề xuất của @arshajii . Điều này không phải là khủng khiếp, nhưng cũng không tuyệt vời lắm, vì việc xây dựng regex là một loại tin tặc, và nó liên quan đến việc StringBufferđã lỗi thời trước đây StringBuilder.

  • Sử dụng giải pháp đệ quy được đề xuất bởi @mjolka , bằng cách chia chuỗi ở các mẫu phù hợp và đệ quy trên các phân đoạn còn lại. Đây là một giải pháp tốt, nhỏ gọn và khá thanh lịch. Điểm yếu của nó là có thể có nhiều hoạt động chuỗi con và nối chuỗi và giới hạn kích thước ngăn xếp áp dụng cho tất cả các giải pháp đệ quy

  • Tách văn bản thành các từ và sử dụng các luồng Java 8 để thực hiện thay thế một cách thanh lịch như @msandiford đã đề xuất, nhưng tất nhiên chỉ hoạt động nếu bạn ổn với việc phân tách tại các ranh giới từ, điều này không phù hợp như một giải pháp chung

Đây là phiên bản của tôi, dựa trên những ý tưởng mượn từ triển khai của Apache . Nó không đơn giản cũng không thanh lịch, nhưng nó hoạt động, và nên tương đối hiệu quả, không cần các bước không cần thiết. Tóm lại, nó hoạt động như thế này: liên tục tìm thấy mẫu tìm kiếm phù hợp tiếp theo trong văn bản và sử dụng a StringBuilderđể tích lũy các phân đoạn chưa từng có và các thay thế.

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

Bài kiểm tra đơn vị:

@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

Tìm kiếm từ đầu tiên được thay thế. Nếu nó nằm trong chuỗi, hãy lặp lại phần của chuỗi trước khi xảy ra và trên phần của chuỗi sau khi xảy ra.

Nếu không, tiếp tục với từ tiếp theo sẽ được thay thế.

Một triển khai ngây thơ có thể trông như thế này

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

Sử dụng mẫu:

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

Đầu ra:

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

Một phiên bản ít ngây thơ hơn:

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

Thật không may, Java Stringkhông có indexOf(String str, int fromIndex, int toIndex)phương pháp. Tôi đã bỏ qua việc thực hiện indexOfở đây vì tôi không chắc nó đúng, nhưng nó có thể được tìm thấy trên ideone , cùng với một số thời gian sơ bộ của các giải pháp khác nhau được đăng ở đây.


2
Mặc dù sử dụng một thư viện hiện có như apache commons cho những thứ như thế này chắc chắn là cách dễ nhất để giải quyết vấn đề khá phổ biến này, bạn đã cho thấy một triển khai hoạt động trên các phần của từ, trên các từ được quyết định trong thời gian chạy và không thay thế các chuỗi bằng mã thông báo ma thuật không giống như (hiện tại) câu trả lời bình chọn cao hơn. +1
Buhb

Đẹp, nhưng chạm đất khi một tệp đầu vào 100 mb được cung cấp.
Barshe De Troyer

12

Một lớp lót trong 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());
  • Biểu thức chính quy của Lookaround ( ?<=, ?=): http://www.THER-expressions.info/lookaround.html
  • Nếu các từ có thể chứa các ký tự regex đặc biệt, hãy sử dụng Pattern.quote để thoát chúng.
  • Tôi sử dụng ổi ImmutableMap cho sự đồng nhất, nhưng rõ ràng bất kỳ Bản đồ nào khác cũng sẽ làm công việc đó.

11

Đây là một khả năng luồng Java 8 có thể thú vị đối với một số người:

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

Đây là một xấp xỉ của cùng một thuật toán trong 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
Đây là một gợi ý hay khi nội dung bạn muốn thay thế là các từ thực tế được phân tách bằng dấu cách (hoặc tương tự), nhưng điều này sẽ không hoạt động để thay thế các từ của từ.
Simon Forsberg

+1 cho luồng Java8. Quá tệ điều này đòi hỏi một dấu phân cách.
Navin

6

Nếu bạn muốn thay thế các từ trong một câu được phân tách bằng khoảng trắng như trong ví dụ của bạn, bạn có thể sử dụng thuật toán đơn giản này.

  1. Câu chuyện chia nhỏ trên không gian trắng
  2. Thay thế từng yếu tố, nếu foo thay thế nó thành thanh và ngược lại
  3. Nối mảng lại thành một chuỗi

Nếu Chia tách trên không gian không được chấp nhận, người ta có thể theo thuật toán thay thế này. Bạn cần sử dụng chuỗi dài hơn trước. Nếu các chuỗi là foo và đánh lừa, bạn cần sử dụng đánh lừa trước và sau đó là foo.

  1. Tách từ foo
  2. Thay thế thanh bằng foo từng phần tử của mảng
  3. Tham gia mảng đó trở lại thanh thêm sau mỗi phần tử ngoại trừ phần tử cuối cùng

1
Đây là những gì tôi đã suy nghĩ để đề nghị quá. Mặc dù nó thêm một hạn chế rằng văn bản là các từ được bao quanh bởi khoảng trắng. :)
Nhà phát triển Marius ilėnas

@ MariusŽilėnas Tôi đã thêm một thuật toán thay thế.
fastcodejava

5

Đây là một câu trả lời ít phức tạp hơn bằng cách sử dụng Bản đồ.

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

Và phương thức được gọi là

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

Đầu ra là: tuyệt vời là Raffy, Raffy Raffy là tuyệt vời tuyệt vời


1
chạy replaced.replaceAll("Raffy", "Barney");theo cái này sẽ làm cho nó trở nên ... chờ nó; Dary !!!
Keale

3

Nếu bạn muốn có thể xử lý nhiều lần xuất hiện của chuỗi tìm kiếm để thay thế, bạn có thể thực hiện điều đó một cách dễ dàng bằng cách tách chuỗi trên mỗi cụm từ tìm kiếm, sau đó thay thế nó. Đây là một ví dụ:

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

Bạn có thể hoàn thành mục tiêu của mình với khối mã sau:

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

Nó thay thế các từ bất kể thứ tự. Bạn có thể mở rộng nguyên tắc này thành một phương thức tiện ích, như:

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

Mà sẽ được tiêu thụ như:

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

Điều này hoạt động và đơn giản:

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

Bạn sử dụng nó như thế này:

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

Lưu ý: số lượng này trên Strings không chứa nhân vật \ufdd0, mà là một nhân vật vĩnh viễn dành cho sử dụng nội bộ Unicode (Xem http://www.unicode.org/faq/private_use.html ):

Tôi không nghĩ nó cần thiết, nhưng nếu bạn muốn an toàn tuyệt đối, bạn có thể sử dụng:

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

Trao đổi chỉ một lần xuất hiện

Nếu chỉ có một lần xuất hiện của mỗi chuỗi có thể hoán đổi trong đầu vào, bạn có thể thực hiện các thao tác sau:

Trước khi tiến hành bất kỳ thay thế nào, hãy lấy các chỉ số về sự xuất hiện của các từ. Sau đó, chúng tôi chỉ thay thế từ được tìm thấy tại các chỉ mục này, và không phải tất cả các lần xuất hiện. Giải pháp này sử dụng StringBuildervà không tạo ra Strings trung gian như thế nào String.replace().

Một điều cần lưu ý: nếu các từ có thể hoán đổi có độ dài khác nhau, sau lần thay thế thứ nhất, chỉ số thứ hai có thể thay đổi (nếu từ thứ 1 xuất hiện trước từ thứ 2) chính xác với độ chênh lệch của 2 độ dài. Vì vậy, việc căn chỉnh chỉ mục thứ hai sẽ đảm bảo điều này hoạt động ngay cả khi chúng ta hoán đổi các từ có độ dài khác nhau.

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

Hoán đổi số lần xuất hiện tùy ý

Tương tự như trường hợp trước, trước tiên chúng ta sẽ thu thập các chỉ mục (lần xuất hiện) của các từ, nhưng trong trường hợp này, nó sẽ liệt kê một số nguyên cho mỗi từ, không chỉ một int. Đối với điều này, chúng tôi sẽ sử dụng phương pháp tiện ích sau:

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

Và bằng cách sử dụng từ này, chúng tôi sẽ thay thế các từ bằng một từ khác bằng cách giảm chỉ số (có thể yêu cầu thay thế giữa 2 từ có thể hoán đổi) để chúng tôi thậm chí sẽ không phải sửa các chỉ số sau khi thay thế:

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

Tôi không chắc cách java xử lý unicode, nhưng tương đương C # của mã này sẽ không chính xác. Vấn đề là chuỗi con indexOfphù hợp có thể không có cùng độ dài với chuỗi tìm kiếm nhờ các đặc điểm riêng của tương đương chuỗi unicode.
CodeInChaos

@CodesInChaos Nó hoạt động hoàn hảo trong Java vì Java Stringlà mảng ký tự chứ không phải mảng byte. Tất cả các phương thức StringStringBuilderhoạt động trên các ký tự không phải trên byte, là "không mã hóa". Do đó, các indexOfkết quả khớp có độ dài (ký tự) chính xác giống như các chuỗi tìm kiếm.
icza

Trong cả C # và java, một chuỗi là một chuỗi các đơn vị mã UTF-16. Vấn đề là có các chuỗi mã khác nhau mà unicode coi là tương đương. Ví dụ, äcó thể được mã hóa dưới dạng một mật mã đơn hoặc atheo sau là sự kết hợp ¨. Ngoài ra còn có một số điểm mã bị bỏ qua, chẳng hạn như các phép nối không có độ rộng bằng không (không). Không có vấn đề gì nếu chuỗi bao gồm byte, ký tự hoặc bất cứ điều gì, nhưng quy tắc so sánh nào indexOfsử dụng. Nó có thể sử dụng đơn giản mã đơn vị bằng cách so sánh đơn vị mã ("Thông thường") hoặc nó có thể thực hiện tương đương unicode. Tôi không biết java nào đã chọn.
CodeInChaos

Ví dụ "ab\u00ADc".IndexOf("bc")trả về 1trong .net khớp chuỗi hai ký tự bcvới chuỗi ba ký tự.
CodeInChaos

1
@CodesInChaos Tôi hiểu ý của bạn bây giờ. Trong Java "ab\u00ADc".indexOf("bc")trả về -1có nghĩa "bc"là không tìm thấy trong "ab\u00ADc". Vì vậy, vẫn có nghĩa là trong Java, thuật toán trên hoạt động, các indexOf()kết quả khớp có độ dài (ký tự) chính xác giống như các chuỗi tìm kiếm và indexOf()chỉ báo cáo trùng khớp nếu các kết quả (mã hóa) khớp.
icza

2

Thật dễ dàng để viết một phương pháp để làm điều này bằng cách sử dụng 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();
}

Kiểm tra:

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

Đầu ra:

Có ba con chó và hai nụ.

Nó không rõ ràng ngay lập tức, nhưng một chức năng như thế này vẫn có thể phụ thuộc vào thứ tự thay thế được chỉ định. Xem xét:

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

Đầu ra:

Java là JavaScript vì Ham là Hamster

Nhưng đảo ngược sự thay thế:

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

Đầu ra:

Java là JavaScript vì Ham là HamScript

Giáo sư! :)

Do đó, đôi khi rất hữu ích để đảm bảo tìm kiếm kết quả khớp dài nhất ( strtrví dụ như chức năng của PHP ). Phiên bản của phương thức này sẽ làm điều đó:

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

Lưu ý rằng các phương pháp trên là trường hợp nhạy cảm. Nếu bạn cần một phiên bản không phân biệt chữ hoa chữ thường, bạn có thể dễ dàng sửa đổi phần trên vì String.regionMatchescó thể lấy ignoreCasetham số.


2

Nếu bạn không muốn bất kỳ sự phụ thuộc nào, bạn chỉ có thể sử dụng một mảng chỉ cho phép thay đổi một lần. Đây không phải là giải pháp hiệu quả nhất, nhưng nó sẽ hoạt động.

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

Sau đó, nó sẽ làm việc.

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

Bạn đang thực hiện nhiều thao tác tìm kiếm thay thế trên đầu vào. Điều này sẽ tạo ra kết quả không mong muốn khi chuỗi thay thế chứa chuỗi tìm kiếm. Hãy xem xét ví dụ về foo-> bar, bar-foo, đây là kết quả cho mỗi lần lặp:

  1. Ngày xửa ngày xưa, có một foo và một quán bar. (đầu vào)
  2. Ngày xửa ngày xưa, có một quán bar và một quán bar. (foo-> thanh)
  3. Ngày xửa ngày xưa, có một foo và một foo. (thanh-> foo, đầu ra)

Bạn cần thực hiện thay thế trong một lần lặp mà không quay lại. Một giải pháp vũ phu như sau:

  1. Tìm kiếm đầu vào từ vị trí hiện tại để kết thúc cho nhiều chuỗi tìm kiếm cho đến khi tìm thấy kết quả khớp
  2. Thay thế chuỗi tìm kiếm phù hợp bằng chuỗi thay thế tương ứng
  3. Đặt vị trí hiện tại cho ký tự tiếp theo sau chuỗi thay thế
  4. Nói lại

Một chức năng như String.indexOfAny(String[]) -> int[]{index, whichString}sẽ hữu ích. Đây là một ví dụ (không phải là hiệu quả nhất):

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

Một số xét nghiệm:

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 trên IDEONE
Demo trên IDEONE, mã thay thế


1

Bạn luôn có thể thay thế nó bằng một từ mà bạn chắc chắn sẽ xuất hiện ở bất kỳ nơi nào khác trong chuỗi và sau đó thực hiện thay thế thứ hai sau:

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

Lưu ý rằng điều này sẽ không hoạt động ngay nếu "StringYouAreSureWillNeverOccur"xảy ra.


5
Sử dụng các ký tự từ Vùng sử dụng riêng tư Unicode, U + E000..U + F8FF, tạo StringThatCannotEverOccur. Bạn có thể lọc chúng ra trước vì chúng không tồn tại trong đầu vào.
David Conrad

Hoặc U + FDD0..U + FDEF, "Không ký tự", được dành riêng cho sử dụng nội bộ.
David Conrad

1

Cân nhắc sử dụng StringBuilder

Sau đó lưu trữ chỉ mục nơi mỗi chuỗi nên bắt đầu. Nếu bạn sử dụng một ký tự giữ chỗ ở mỗi vị trí, sau đó xóa nó và chèn chuỗi người dùng. Sau đó, bạn có thể ánh xạ vị trí kết thúc bằng cách thêm độ dài chuỗi vào vị trí bắt đầu.

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

Những gì tôi chỉ có thể chia sẻ là phương pháp của riêng tôi.

Bạn có thể sử dụng tạm thời String temp = "<?>";hoặcString.Format();

Đây là mã ví dụ của tôi được tạo trong ứng dụng bảng điều khiển thông qua - "Chỉ ý tưởng, không trả lời chính xác" .

static void Main(string[] args)
    {
        String[] word1 = {"foo", "Once"};
        String[] word2 = {"bar", "time"};
        String story = "Once upon a time, there was a foo and a bar.";

        story = Switcher(story,word1,word2);
        Console.WriteLine(story);
        Console.Read();
    }
    // Using a temporary string.
    static string Switcher(string text, string[] target, string[] value)
    {
        string temp = "<?>";
        if (target.Length == value.Length)
        {
            for (int i = 0; i < target.Length; i++)
            {
                text = text.Replace(target[i], temp);
                text = text.Replace(value[i], target[i]);
                text = text.Replace(temp, value[i]);
            }
        }
        return text;
    }

Hoặc bạn cũng có thể sử dụng 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;
        }

Đầu ra: time upon a Once, there was a bar and a foo.


Nó khá là hacky. Bạn sẽ làm gì nếu anh ấy muốn thay thế "_"?
Cầu tàu-Alexandre Bouchard

@ Pier-AlexandreBouchard Trong các phương pháp tôi thay đổi giá trị temptừ "_"thành <?>. Nhưng nếu cần, những gì anh ta có thể làm là thêm một tham số khác vào phương thức sẽ thay đổi temp. - "tốt hơn là giữ cho nó đơn giản phải không?"
Leonel Sarmiento

Quan điểm của tôi là yon không thể đảm bảo kết quả mong đợi vì nếu temp == thay thế, cách của bạn sẽ không hoạt động.
Cầu tàu-Alexandre Bouchard

1

Đây là phiên bản của tôi, dựa trên từ:

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

Cách nhỏ khó khăn nhưng bạn cần phải làm thêm một số kiểm tra.

Chuyển đổi chuỗi thành mảng ký tự

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

2.loop trên tạm thời và thay thế foovới barbarvới foonhư không có cơ hội nhận được chuỗi có thể thay thế một lần nữa.


1

Chà, câu trả lời ngắn hơn là ...

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

Sử dụng câu trả lời được tìm thấy ở đây bạn có thể tìm thấy tất cả các lần xuất hiện của chuỗi bạn muốn thay thế.

Vì vậy, ví dụ bạn chạy mã trong câu trả lời SO ở trên. Tạo hai bảng chỉ mục (giả sử thanh và foo không chỉ xuất hiện một lần trong chuỗi của bạn) và bạn có thể làm việc với các bảng đó để thay thế chúng trong chuỗi của mình.

Bây giờ để thay thế trên các vị trí chỉ mục cụ thể, bạn có thể sử dụng:

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

Trong khi đó poslà chỉ mục nơi chuỗi của bạn bắt đầu (từ các bảng chỉ mục tôi đã trích dẫn ở trên). Vì vậy, giả sử bạn đã tạo hai bảng chỉ mục cho mỗi bảng. Hãy gọi cho họ indexBarindexFoo.

Bây giờ trong việc thay thế chúng, bạn có thể chỉ cần chạy hai vòng, một vòng cho mỗi lần thay thế bạn muốn thực hiện.

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

Tương tự một vòng lặp khác cho indexFoo.

Điều này có thể không hiệu quả như các câu trả lời khác ở đây nhưng nó dễ hiểu hơn so với Bản đồ hoặc các nội dung khác.

Điều này sẽ luôn cung cấp cho bạn kết quả bạn muốn và cho nhiều lần xuất hiện có thể của mỗi chuỗi. Miễn là bạn lưu trữ chỉ số của mỗi lần xuất hiện.

Ngoài ra câu trả lời này không cần đệ quy cũng như bất kỳ phụ thuộc bên ngoài. Theo như độ phức tạp thì nó có thể là O (n bình phương), trong khi n là tổng số lần xuất hiện của cả hai từ.


-1

Tôi đã phát triển mã này sẽ giải quyết vấn đề:

public static String change(String s,String s1, String s2) {
   int length = s.length();
   int x1 = s1.length();
   int x2 = s2.length();
   int x12 = s.indexOf(s1);
   int x22 = s.indexOf(s2);
   String s3=s.substring(0, x12);
   String s4 =s.substring(x12+3, x22);
   s=s3+s2+s4+s1;
   return s;
}

Trong sử dụng chính change(story,word2,word1).


2
Nó sẽ chỉ hoạt động nếu có chính xác một lần xuất hiện của mỗi chuỗi
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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.