다음 코드가 있다고 가정 해 봅시다.
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."
나는 그것을 달성 할 수 있는가?
답변
Apache Commons StringUtils 의 replaceEach()
메소드를 사용하십시오 .
StringUtils.replaceEach(story, new String[]{"foo", "bar"}, new String[]{"bar", "foo"})
답변
문장에 아직없는 중간 값을 사용합니다.
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)의 문자를 사용하십시오. 이러한 문자는 합법적으로 입력에 포함되어서는 안되므로 (일부 응용 프로그램 내에서 응용 프로그램 특정 의미 만 있음) 해당 문자를 먼저 제거한 다음 교체 할 때 자리 표시 자로 사용하십시오.
답변
Matcher#appendReplacement
and를 사용하여 이와 같은 것을 시도 할 수 있습니다 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가있었습니다.
답변
이것은 쉬운 문제가 아닙니다. 검색 대체 매개 변수가 많을수록 더 까다로워집니다. 추악하고 우아하고 효율적으로 낭비되는 팔레트에 여러 가지 옵션이 있습니다.
-
@AlanHay가 권장 되는대로
StringUtils.replaceEach
Apache Commons에서 사용하십시오 . 프로젝트에 새로운 의존성을 추가 할 수 있다면 좋은 옵션입니다. 운이 좋을 수도 있습니다 : 종속성이 이미 프로젝트에 포함될 수 있습니다 -
@Jeroen이 제안한 대로 임시 자리 표시자를 사용하고 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"})
);
}
답변
대체 할 첫 단어를 검색하십시오. 문자열에 있으면 발생 전 문자열 부분과 발생 후 문자열 부분에서 되풀이하십시오.
그렇지 않으면 다음 단어를 계속 바꾸십시오.
순진한 구현은 다음과 같습니다.
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 에서 찾을 수 있습니다 .
답변
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을 사용하지만 분명히 다른 모든지도 잘 작동합니다.
답변
다음은 일부 사용자에게 흥미로운 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);
