한 줄이 다른 줄을 바꾸지 않는 방법으로 두 줄을 어떻게 바꿀 수 있습니까?


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
하나는 확실히 몇 가지 기능이 있어야 swap(String s1, String s2, String s3)그 스왑 모두의 발생 s2s3, 그 반대는 마찬가지.
Ryan

입력에서 각 교체 가능한 단어가 한 번만 발생한다고 가정 할 수 있습니까?
icza

14
코너 케이스 : "ababababababa"에서 "ab"와 "ba"를 교체 할 때 출력으로 무엇을 기대합니까?
Hagen von Eitzen

1
아래에 좋은 해결책이 있지만 왜 접근 방식이 효과가 없는지 이해합니까? 먼저, "foo와 bar가 있습니다". 첫 번째 교체 ( "foo"-> "bar") 후에 "막대와 막대가 있습니다". 이제 "bar"가 2 번 발생하므로 두 번째 교체가 예상 한대로 수행되지 않습니다. 지난 번에 교체하지 않은 교체 만 알고 싶을 방법이 없습니다. @HagenvonEitzen 재미있는. 작동하는 솔루션이 찾은 첫 번째 문자열과 일치하고 교체 한 다음 교체 된 섹션의 끝에서 반복 될 것으로 기대합니다.
DeveloperInDevelopment 1

1
Jeroen의 솔루션은 대량 이름 변경을 수행해야 할 때 텍스트 편집기에서 자주 사용하는 솔루션입니다. 간단하고 이해하기 쉽고 특별한 라이브러리가 필요하지 않으며 약간의 생각만으로도 쉽게 사용할 수 있습니다.
핫 릭

답변:


88

Apache Commons StringUtilsreplaceEach()메소드를 사용하십시오 .

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

2
내부적으로 각각 정확히 무엇을 대체하는지 알고 있습니까?
Marek

3
@Marek 함수가 검색 한 각 항목을 색인화 한 다음 모두 색인화되면 대체합니다.

16
당신은이에 대한 소스를 찾을 수 있습니다 여기에 주위 4684. 라인
제론 Vannevel

null그래도 통과 하지 않으면 안타깝습니다 .
rightfold

87

문장에 아직없는 중간 값을 사용합니다.

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

비판에 대한 응답으로 : zq515sqdqs5d5sq1dqs4d1q5dqqé "& é5d4sqjshsjddjhodfqsqc, nvùq ^ µù; d & € sdq : d :;) àçàçlala 와 같이 충분히 드문 문자열을 사용하는 경우 에는 아무 말도 하지 않습니다 소스 코드를 알고 그 시점에서 다른 수준의 걱정을 겪는 것이 사용자의 유무를 알 수있는 유일한 방법입니다.

예, 아마도 멋진 정규식 방법이있을 수 있습니다. 나는 읽을 수있는 것을 선호한다.

또한 의견@David Conrad가 제공 한 훌륭한 조언을 반복합니다 .

가능성이없는 것으로 현명하게 (멍청하게) 선택된 문자열을 사용하지 마십시오. 유니 코드 개인 사용 영역 (U + E000..U + F8FF)의 문자를 사용하십시오. 이러한 문자는 합법적으로 입력에 포함되어서는 안되므로 (일부 응용 프로그램 내에서 응용 프로그램 특정 의미 만 있음) 해당 문자를 먼저 제거한 다음 교체 할 때 자리 표시 자로 사용하십시오.


4
@arshajii 나는 그것이 "더 나은"에 대한 당신의 정의에 달려 있다고 생각합니다 ... 그것이 작동하고 수용할만한 성능을 발휘한다면, 다음 프로그래밍 작업으로 넘어 가서 나중에 리팩토링 중에 개선하십시오.
Matt Coubrough

24
분명히 "lala"는 예일뿐입니다. 프로덕션에서는 " zq515sqdqs5d5sq1dqs4d1q5dqqé"& é & € sdq : d :;) àçàçlala "를 사용해야 합니다.
Jeroen Vannevel

81
가능성이없는 것으로 현명하게 (멍청하게) 선택된 문자열을 사용하지 마십시오. 유니 코드 개인 사용 영역 (U + E000..U + F8FF)의 문자를 사용하십시오. 이러한 문자는 합법적으로 입력에 포함되지 않아야하기 때문에 먼저 제거하고 (일부 응용 프로그램 내에서 응용 프로그램 특정 의미 만 있음) 교체 할 때 자리 표시 자로 사용하십시오.
David Conrad

22
실제로 유니 코드 FAQ를 읽은 후에 U + FDD0..U + FDEF 범위의 비 문자가 더 나은 선택이라고 생각합니다.
David Conrad

6
@Taemyr 물론이지만 누군가가 입력을 소독해야합니다. 문자열 대체 함수는 모든 문자열에서 작동하지만이 함수는 안전하지 않은 입력으로 인해 중단됩니다.
Navin

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());
옛날 옛적에 술집과 foo가있었습니다.

2
이 일 경우합니까 foo, bar그리고 story모두가 알 수없는 값이?
Stephen P

1
나는 기본적으로 하드 코딩 한 @StephenP "foo""bar"영업 이익이 자신의 코드에 있었다으로 대체 문자열을하지만, 접근 방식의 동일한 유형의 잘 그 가치를 알 수없는 경우에도 작동합니다 (당신은 사용해야 할 것 if/ else if대신의 switchwhile-고리).
arshajii

6
정규식을 만들 때주의해야합니다. Pattern.quote편리하게, 또는 올 것이다 \Q하고 \E.
David Conrad

1
@arshajii-yep, word1, word2 및 story를 매개 변수로 사용하는 "swapThese"방법으로 스스로 증명했습니다. +1
Stephen P

4
더 깔끔한 것은 패턴을 사용하고 일치하는 단어가 반복되는 것을 피하기 위해 (foo)|(bar)를 검사하는 것 m.group(1) != null입니다.
Jörn Horstmann

32

이것은 쉬운 문제가 아닙니다. 검색 대체 매개 변수가 많을수록 더 까다로워집니다. 추악하고 우아하고 효율적으로 낭비되는 팔레트에 여러 가지 옵션이 있습니다.

  • @AlanHay가 권장 되는대로StringUtils.replaceEach Apache Commons에서 사용하십시오 . 프로젝트에 새로운 의존성을 추가 할 수 있다면 좋은 옵션입니다. 운이 좋을 수도 있습니다 : 종속성이 이미 프로젝트에 포함될 수 있습니다

  • @Jeroen이 제안한 대로 임시 자리 표시자를 사용하고 2 단계로 교체를 수행하십시오.

    1. 모든 검색 패턴을 원본 텍스트에없는 고유 한 태그로 바꿉니다.
    2. 자리 표시자를 실제 대상 교체로 교체

    이는 여러 가지 이유로 큰 접근 방식이 아닙니다. 첫 번째 단계에서 사용 된 태그가 실제로 고유해야합니다. 실제로 필요한 것보다 더 많은 문자열 교체 작업을 수행합니다.

  • 모든 패턴에서 정규식을 구축하고 함께 방법을 사용 Matcher하고StringBuffer 등이 제안 @arshajii . 이 끔찍한되지 않습니다,하지만 그 큰 중 하나, 정규식을 구축하는 것은 일종의 hackish의, 그리고이 포함로 StringBuffer찬성 얼마 전 패션 A의 나갔다한다 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 String에는 indexOf(String str, int fromIndex, int toIndex)방법 이 없습니다 . 나는 indexOf그것이 정확하지 않다고 여기 에서 구현을 생략 했지만 여기에 게시 된 다양한 솔루션의 대략적인 타이밍과 함께 ideone 에서 찾을 수 있습니다 .


2
아파치 커먼즈와 같은 기존 라이브러리를 사용 하여이 일반적인 문제를 해결하는 가장 쉬운 방법은 의심 할 여지가 없지만 단어의 일부, 런타임에 결정된 단어 및 하위 문자열을 마술 토큰으로 바꾸지 않고 단어에서 작동하는 구현을 보여주었습니다 (현재) 더 높은 투표 답변. +1
Buhb

아름답지만 100MB의 입력 파일이 제공되면 땅에 닿습니다.
Christophe De Troyer

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());
  • Lookaround 정규 표현식 ( ?<=, ?=) : http://www.regular-expressions.info/lookaround.html
  • 단어에 특수 정규식 문자가 포함될 수 있으면 Pattern.quote 를 사용 하여 이스케이프 처리하십시오.
  • 간결함을 위해 구아바 ImmutableMap을 사용하지만 분명히 다른 모든지도 잘 작동합니다.

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

Java8 스트림의 경우 +1 너무 나쁘면 구분자가 필요합니다.
Navin

6

예와 같이 공백으로 구분 된 문장에서 단어를 바꾸려면이 간단한 알고리즘을 사용할 수 있습니다.

  1. 공백 분할 스토리
  2. foo가 bar로 바뀔 경우 각 요소를 바꿉니다.
  3. 배열을 하나의 문자열로 다시 결합

공간 분할이 허용되지 않는 경우이 대체 알고리즘을 따를 수 있습니다. 더 긴 문자열을 먼저 사용해야합니다. 문자열이 foo와 멍청한 경우 먼저 멍청이를 사용해야합니다.

  1. 단어 foo로 나누기
  2. 배열의 각 요소를 foo로 바꿉니다.
  3. 마지막 요소를 제외한 각 요소 다음에 해당 배열을 다시 추가하십시오.

1
이것은 내가 제안하려고 생각한 것입니다. 텍스트가 공백으로 둘러싸인 단어라는 제한이 추가되었지만. :)
개발자 Marius Žilėnas

@ MariusŽilėnas 대안 알고리즘을 추가했습니다.
fastcodejava 5

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

결과 : awesome은 Raffy, Raffy Raffy는 굉장합니다


1
replaced.replaceAll("Raffy", "Barney");이것을 실행 하면 다리가 튼튼해질 것입니다 ... 기다리십시오. 고마워 !!!
Keale

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합니다. 문자는 유니 코드에서 내부 용 으로 영구적으로 예약 한 문자입니다 ( http://www.unicode.org/faq/private_use.html 참조 ).

나는 그것이 필요하다고 생각하지 않지만, 절대 안전을 원한다면 다음을 사용할 수 있습니다.

public String replaceBoth(String text, String token1, String token2) {
    if (text.contains("\ufdd0") || token1.contains("\ufdd0") || token2.contains("\ufdd0")) throw new IllegalArgumentException("Invalid character.");
    return text.replace(token1, "\ufdd0").replace(token2, token1).replace("\ufdd0", token2);
    }

3

한 번만 발생

입력에 각 스왑 가능한 문자열이 한 번만 나타나는 경우 다음을 수행 할 수 있습니다.

바꾸기를 진행하기 전에 단어의 출현 지수를 얻으십시오. 그 후 우리는 이러한 색인에서 찾은 단어 만 바꾸고 모든 경우가 아닙니다. 이 솔루션은 같은 StringBuilder중간체를 사용 하거나 생성하지 않습니다 .StringString.replace()

한 가지 알아 두어야 할 사항 : 교체 가능한 단어의 길이가 다른 경우 첫 번째 교체 후 두 번째 색인이 첫 번째 단어가 두 번째 이전에 나타나는 경우 두 길이의 차이와 정확히 일치 할 수 있습니다. 따라서 두 번째 색인을 정렬하면 길이가 다른 단어를 바꾸는 경우에도 작동합니다.

public static String swap(String src, String s1, String s2) {
    StringBuilder sb = new StringBuilder(src);
    int i1 = src.indexOf(s1);
    int i2 = src.indexOf(s2);

    sb.replace(i1, i1 + s1.length(), s2); // Replace s1 with s2
    // If s1 was before s2, idx2 might have changed after the replace
    if (i1 < i2)
        i2 += s2.length() - s1.length();
    sb.replace(i2, i2 + s2.length(), s1); // Replace s2 with s1

    return sb.toString();
}

임의의 발생 횟수 스와핑

이전의 경우와 유사하게 먼저 단어의 색인 (발생)을 수집하지만이 경우에는 단어가 아닌 각 단어의 정수 목록이 int됩니다. 이를 위해 다음과 같은 유틸리티 방법을 사용합니다.

public static List<Integer> occurrences(String src, String s) {
    List<Integer> list = new ArrayList<>();
    for (int idx = 0;;)
        if ((idx = src.indexOf(s, idx)) >= 0) {
            list.add(idx);
            idx += s.length();
        } else
            return list;
}

그리고 이것을 사용하여 우리는 교체 후 인덱스를 수정하지 않아도되도록 인덱스를 낮추어 단어를 다른 단어로 대체합니다.

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가 유니 코드를 어떻게 처리하는지 잘 모르겠지만이 코드와 동등한 C #은 올바르지 않습니다. 문제는 indexOf유니 코드 문자열 동등성의 특성 때문에 일치 하는 하위 문자열의 길이가 검색 문자열 과 같지 않을 수 있다는 것입니다.
코드 InChaos

@CodesInChaos Java String는 바이트 배열이 아닌 문자 배열 이기 때문에 Java에서 완벽하게 작동합니다 . 바이트가 아닌 문자에 대한 모든 방법 StringStringBuilder작동은 "인코딩이 필요 없습니다". 따라서 indexOf일치하는 길이는 검색 문자열과 정확히 같은 (문자) 길이입니다.
icza

C #과 java에서 문자열은 UTF-16 코드 단위의 시퀀스입니다. 문제는 유니 코드가 동등한 것으로 간주하는 다른 코드 포인트 시퀀스가 ​​있다는 것입니다. 예를 들어 ä단일 코드 포인트로 또는 a그 뒤에 결합하여 인코딩 할 수 있습니다 ¨. 너비가 0이 아닌 결합 자와 같이 무시되는 일부 코드 포인트도 있습니다. 문자열이 바이트, 문자 또는 기타로 구성되어 있는지 여부는 중요하지만 어떤 비교 규칙이 indexOf사용 되는지는 중요하지 않습니다 . 코드 단위 비교 ( "Ordinal")로 코드 단위를 사용하거나 유니 코드 동등성을 구현할 수 있습니다. 어느 자바를 선택했는지 모르겠습니다.
코드 InChaos

예를 들어 .net에서 두 문자열 을 세 문자열 과 일치시키는 것으로 "ab\u00ADc".IndexOf("bc")반환 합니다. 1bc
코드 InChaos

1
@CodesInChaos 지금 무슨 말인지 알겠습니다. 자바에서 "ab\u00ADc".indexOf("bc")반환 -1수단은 "bc"에서 찾을 수 없습니다 "ab\u00ADc". 따라서 Java에서 위의 알고리즘이 작동하고 indexOf()일치하는 것은 검색 문자열과 정확히 같은 (문자) 길이를 가지며 문자 indexOf()시퀀스 (코드 포인트)가 일치하면 보고서 만 일치합니다.
icza

2

다음을 사용하여이를 수행하는 방법을 쉽게 작성할 수 있습니다 String.regionMatches.

public static String simultaneousReplace(String subject, String... pairs) {
    if (pairs.length % 2 != 0) throw new IllegalArgumentException(
        "Strings to find and replace are not paired.");
    StringBuilder sb = new StringBuilder();
    outer:
    for (int i = 0; i < subject.length(); i++) {
        for (int j = 0; j < pairs.length; j += 2) {
            String find = pairs[j];
            if (subject.regionMatches(i, find, 0, find.length())) {
                sb.append(pairs[j + 1]);
                i += find.length() - 1;
                continue outer;
            }
        }
        sb.append(subject.charAt(i));
    }
    return sb.toString();
}

테스트 :

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

산출:

3 마리의 개와 2 마리의 budgie가 있습니다.

즉시 명백하지는 않지만 이와 같은 기능은 여전히 ​​교체가 지정된 순서에 따라 달라질 수 있습니다. 치다:

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

산출:

Ham은 Hamster와 마찬가지로 Java는 JavaScript를

그러나 교체를 역전하십시오.

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

산출:

Ham은 HamScript와 마찬가지로 Java는 JavaScript를

죄송합니다! :)

따라서 PHP 함수 와 같이 가장 긴 일치 항목 을 찾는 것이 유용한 경우가 strtr있습니다. 이 버전의 메소드는 다음을 수행합니다.

public static String simultaneousReplace(String subject, String... pairs) {
    if (pairs.length % 2 != 0) throw new IllegalArgumentException(
        "Strings to find and replace are not paired.");
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < subject.length(); i++) {
        int longestMatchIndex = -1;
        int longestMatchLength = -1;
        for (int j = 0; j < pairs.length; j += 2) {
            String find = pairs[j];
            if (subject.regionMatches(i, find, 0, find.length())) {
                if (find.length() > longestMatchLength) {
                    longestMatchIndex = j;
                    longestMatchLength = find.length();
                }
            }
        }
        if (longestMatchIndex >= 0) {
            sb.append(pairs[longestMatchIndex + 1]);
            i += longestMatchLength - 1;
        } else {
            sb.append(subject.charAt(i));
        }
    }
    return sb.toString();
}

위의 방법은 대소 문자를 구분합니다. 대소 문자를 구분하지 않는 버전이 필요한 경우 매개 변수를 String.regionMatches사용할 수 있으므로 위의 내용을 쉽게 수정할 수 있습니다 ignoreCase.


2

종속성을 원하지 않으면 일회성 변경 만 허용하는 배열을 사용하면됩니다. 이것은 가장 효율적인 솔루션은 아니지만 작동해야합니다.

public String replace(String sentence, String[]... replace){
    String[] words = sentence.split("\\s+");
    int[] lock = new int[words.length];
    StringBuilder out = new StringBuilder();

    for (int i = 0; i < words.length; i++) {
        for(String[] r : replace){
            if(words[i].contains(r[0]) && lock[i] == 0){
                words[i] = words[i].replace(r[0], r[1]);
                lock[i] = 1;
            }
        }

        out.append((i < (words.length - 1) ? words[i] + " " : words[i]));
    }

    return out.toString();
}

그런 다음 일을해야했습니다.

String story = "Once upon a time, there was a foo and a bar.";

String[] a = {"foo", "bar"};
String[] b = {"bar", "foo"};
String[] c = {"there", "Pocahontas"};
story = replace(story, a, b, c);

System.out.println(story); // Once upon a time, Pocahontas was a bar and a foo.

2

입력에서 여러 검색 바꾸기 작업을 수행하고 있습니다. 대체 문자열에 검색 문자열이 포함되어 있으면 원하지 않는 결과가 발생합니다. foo-> bar, bar-foo 예제를 고려하십시오. 각 반복에 대한 결과는 다음과 같습니다.

  1. 옛날 옛적에 foo와 술집이있었습니다. (입력)
  2. 옛날 옛적에 바와 바가있었습니다. (foo-> 바)
  3. 옛날 옛적에 foo와 foo가있었습니다. (bar-> foo, 출력)

돌아 가지 않고 한 번의 반복으로 교체를 수행해야합니다. 무차별 대입 솔루션은 다음과 같습니다.

  1. 일치하는 것을 찾을 때까지 현재 위치에서 입력을 검색하여 여러 검색 문자열을 종료하십시오.
  2. 일치하는 검색 문자열을 해당하는 대체 문자열로 바꿉니다.
  3. 교체 된 문자열 다음에 현재 위치를 다음 문자로 설정
  4. 반복

같은 함수는 String.indexOfAny(String[]) -> int[]{index, whichString}유용 할 것이다. 다음은 가장 효율적인 예는 아닙니다.

private static String replaceEach(String str, String[] searchWords, String[] replaceWords) {
    String ret = "";
    while (str.length() > 0) {
        int i;
        for (i = 0; i < searchWords.length; i++) {
            String search = searchWords[i];
            String replace = replaceWords[i];
            if (str.startsWith(search)) {
                ret += replace;
                str = str.substring(search.length());
                break;
            }
        }
        if (i == searchWords.length) {
            ret += str.substring(0, 1);
            str = str.substring(1);
        }
    }
    return ret;
}

일부 테스트 :

System.out.println(replaceEach(
    "Once upon a time, there was a foo and a bar.",
    new String[]{"foo", "bar"},
    new String[]{"bar", "foo"}
));
// Once upon a time, there was a bar and a foo.

System.out.println(replaceEach(
    "a p",
    new String[]{"a", "p"},
    new String[]{"apple", "pear"}
));
// apple pear

System.out.println(replaceEach(
    "ABCDE",
    new String[]{"A", "B", "C", "D", "E"},
    new String[]{"B", "C", "E", "E", "F"}
));
// BCEEF

System.out.println(replaceEach(
    "ABCDEF",
    new String[]{"ABCDEF", "ABC", "DEF"},
    new String[]{"XXXXXX", "YYY", "ZZZ"}
));
// XXXXXX
// note the order of search strings, longer strings should be placed first 
// in order to make the replacement greedy

IDEONE 데모 Demoone
데모, 대체 코드


1

당신은 항상 당신이 문자열의 다른 곳에 나타나지 않을 것이라고 확신하는 단어로 바꿀 수 있습니다.

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar."
story = story.replace("foo", "StringYouAreSureWillNeverOccur").replace("bar", "word2").replace("StringYouAreSureWillNeverOccur", "word1");

"StringYouAreSureWillNeverOccur"발생 하면 제대로 작동 하지 않습니다.


5
유니 코드 개인 사용 영역 U + E000..U + F8FF의 문자를 사용하여 StringThatCannotEverOccur를 작성하십시오. 입력에 없어야하므로 미리 필터링 할 수 있습니다.
David Conrad

또는 U + FDD0..U + FDEF ( "비 문자")는 내부 용으로 예약되어 있습니다.
David Conrad

1

사용을 고려하십시오 StringBuilder

그런 다음 각 문자열을 시작해야하는 색인을 저장하십시오. 각 위치에 자리 표시 자 문자를 사용하는 경우이를 제거하고 사용자 문자열을 삽입하십시오. 그런 다음 문자열 길이를 시작 위치에 추가하여 끝 위치를 매핑 할 수 있습니다.

String firstString = "???";
String secondString  = "???"

StringBuilder story = new StringBuilder("One upon a time, there was a " 
    + firstString
    + " and a "
    + secondString);

int  firstWord = 30;
int  secondWord = firstWord + firstString.length() + 7;

story.replace(firstWord, firstWord + firstString.length(), userStringOne);
story.replace(secondWord, secondWord + secondString.length(), userStringTwo);

firstString = userStringOne;
secondString = userStringTwo;

return story;

1

내가 공유 할 수있는 것은 내 자신의 방법입니다.

임시 String temp = "<?>";또는String.Format();

이것은 콘솔 응용 프로그램에서 만든 예제 코드입니다. - "아이디어 만, 정답은 아님" .

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

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

또는 당신은 또한 사용할 수 있습니다 String.Format();

static string Switcher(string text, string[] target, string[] value)
        {
            if (target.Length == value.Length)
            {
                for (int i = 0; i < target.Length; i++)
                {
                    text = text.Replace(target[i], "{0}").Replace(value[i], "{1}");
                    text = String.Format(text, value[i], target[i]);
                }
            }
            return text;
        }

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


꽤 해키입니다. "_"를 바꾸려면 어떻게 하시겠습니까?
Pier-Alexandre Bouchard

@ Pier-AlexandreBouchard 방법에서 값을 temp에서 "_"로 변경합니다 <?>. 그러나 필요한 경우 그가 할 수있는 일은 온도를 변경하는 다른 매개 변수를 메소드에 추가하는 것입니다. - "간단하게 유지하는 것이 낫습니까?"
Leonel Sarmiento

내 요지는 temp == 교체하면 길이 작동하지 않기 때문에 예상 결과를 보장 할 수 없다는 것입니다.
Pier-Alexandre Bouchard 14

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 및 교체 foo와 함께 barbar함께 foo다시 교체 문자열을 점점 더 기회가 없기 때문에.


1

짧은 대답은 ...

String word1 = "bar";
String word2 = "foo";
String story = "Once upon a time, there was a foo and a bar.";
story = story.replace("foo", "@"+ word1).replace("bar", word2).replace("@" + word2, word1);
System.out.println(story);

1

여기에 있는 대답을 사용하면 바꿀 문자열이 모두 나타 납니다 .

예를 들어 위의 SO 답변에서 코드를 실행하십시오. 두 개의 색인 테이블을 작성하십시오 (bar와 foo는 문자열에 한 번만 표시되지 않음).이 테이블을 사용하여 문자열에서 대체 할 수 있습니다.

이제 특정 색인 위치를 대체하기 위해 다음을 사용할 수 있습니다.

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

반면 pos문자열은 위에서 인용 한 색인 테이블에서 시작하는 색인입니다. 각각에 대해 두 개의 인덱스 테이블을 생성했다고 가정하겠습니다. 의 그들을 부르 자 indexBarindexFoo.

이제 교체 할 때마다 교체 할 루프마다 하나씩 두 개의 루프를 실행할 수 있습니다.

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

마찬가지로 다른 루프입니다 indexFoo.

이것은 다른 답변만큼 효율적이지 않을 수 있지만지도 또는 다른 것들보다 이해하기가 더 간단합니다.

이렇게하면 항상 원하는 결과를 얻을 수 있고 각 문자열이 여러 번 나타날 수 있습니다. 각 발생의 색인을 저장하는 한.

또한이 대답은 재귀 나 외부 종속성이 필요하지 않습니다. 복잡성이 제대로되는 한 O (n squared)이고 n은 두 단어의 합계입니다.


-1

이 코드를 개발하여 문제를 해결할 수 있습니다.

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

주요 용도 change(story,word2,word1).


2
각 줄의 모양이 정확히 하나 인 경우에만 작동합니다.
Vic

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