import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
class Main {
private final String expected
; private final String[] search
= new String[] { "foo",
"bar",
}; private final String[] replace
= new String[] { "bar",
"foo" };
private Main() {
StringBuilder sb = new StringBuilder();
StringBuilder expectedSb = new StringBuilder();
for (int i = 0; i < 320000; i++) {
sb.append("ffffffffffoo baz quux bar");
expectedSb.append("fffffffffbar baz quux foo");
}
this.input = sb.toString();
this.expected = expectedSb.toString();
System.
out.
printf("Input size: %d chars%n",
this.
input.
length()); }
public static void main
(String[] args
) { Solution[] solutions = new Solution[] {
new RecursiveSolution(),
new JanosSolution(),
new ArshajiiSolution(),
new BoannSolution(),
new JeroenSolution(),
new ApacheSolution()
};
Main main = new Main();
for (int i = 0; i < 2; i++) {
for (Solution solution : solutions) {
main.timeSolution(solution);
}
}
}
private void timeSolution(Solution solution) {
long start
= System.
nanoTime(); String actual
= solution.
replaceAll(input, search, replace
); System.
out.
printf("%20s: %12d. Equals expected: %b%n", solution.
getName(), now
- start, actual.
equals(this.
expected)); }
private interface Solution {
}
private static class RecursiveSolution implements Solution {
return "Recursive";
}
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);
}
private static int indexOf
(final String source,
final String target,
final int start,
final int end
) { int len = target.length();
if (len == 0) {
return start;
}
if (len > source.length()) {
return -1;
}
if (len == source.length()) {
return source.equals(target) ? 0 : -1;
}
int max = end - len;
char first = target.charAt(0);
for (int i = start; i <= max; i++) {
if (source.charAt(i) != first) {
while (++i <= max && source.charAt(i) != first);
}
if (i <= max) {
int j = i + 1;
int e = j + len - 1;
for (int k = 1; j < e && source.charAt(j) == target.charAt(k); j++, k++);
if (j == e) {
return i;
}
}
}
return -1;
}
}
private static class JeroenSolution implements Solution {
return "Jeroen";
}
subject = subject.replace("foo", "\uE000");
subject = subject.replace("bar", "foo");
subject = subject.replace("\uE000", "bar");
return subject;
}
}
private static class JanosSolution implements Solution {
return "Janos";
}
// 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 Map
<String, String
> patternToReplacement
= new HashMap
<>(); private final Set<String> pendingPatterns = new HashSet<>();
private MatchInfo matchInfo = null;
private static class MatchInfo {
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;
}
}
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;
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;
}
}
}
private static class ArshajiiSolution implements Solution {
return "arshajii";
}
Pattern p = Pattern.compile("foo|bar");
Matcher m = p.matcher(input);
while (m.find()) {
/* do the swap... */
switch (m.group()) {
case "foo":
m.appendReplacement(sb, "bar");
break;
case "bar":
m.appendReplacement(sb, "foo");
break;
default:
/* error */
break;
}
}
m.appendTail(sb);
return sb.toString();
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://w...content-available-to-author-only...e.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// From http://s...content-available-to-author-only...e.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/StringUtils.java?view=co
private static class ApacheSolution implements Solution {
return "Apache";
}
return replaceEach(text, searchList, replacementList, false, 0);
}
private static String replaceEach
( final String text,
final String[] searchList,
final String[] replacementList,
final boolean repeat,
final int timeToLive
) {
// mchyzer Performance note: This creates very few new objects (one major goal)
// let me know if there are performance requests, we can create a harness to measure
if (text == null || text.isEmpty() || searchList == null ||
searchList.length == 0 || replacementList == null || replacementList.length == 0) {
return text;
}
// if recursing, this shouldn't be less than 0
if (timeToLive < 0) {
"output of one loop is the input of another");
}
final int searchLength = searchList.length;
final int replacementLength = replacementList.length;
// make sure lengths are ok, these need to be equal
if (searchLength != replacementLength) {
+ searchLength
+ " vs "
+ replacementLength);
}
// keep track of which still have matches
final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];
// index on index that the match was found
int textIndex = -1;
int replaceIndex = -1;
int tempIndex = -1;
// index of replace array that will replace the search string found
// NOTE: logic duplicated below START
for (int i = 0; i < searchLength; i++) {
if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
searchList[i].isEmpty() || replacementList[i] == null) {
continue;
}
tempIndex = text.indexOf(searchList[i]);
// see if we need to keep searching for this
if (tempIndex == -1) {
noMoreMatchesForReplIndex[i] = true;
} else {
if (textIndex == -1 || tempIndex < textIndex) {
textIndex = tempIndex;
replaceIndex = i;
}
}
}
// NOTE: logic mostly below END
// no search strings found, we are done
if (textIndex == -1) {
return text;
}
int start = 0;
// get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit
int increase = 0;
// count the replacement text elements that are larger than their corresponding text being replaced
for (int i = 0; i < searchList.length; i++) {
if (searchList[i] == null || replacementList[i] == null) {
continue;
}
final int greater = replacementList[i].length() - searchList[i].length();
if (greater > 0) {
increase += 3 * greater; // assume 3 matches
}
}
// have upper-bound at 20% increase, then let Java take over
increase
= Math.
min(increase, text.
length() / 5);
final StringBuilder buf = new StringBuilder(text.length() + increase);
while (textIndex != -1) {
for (int i = start; i < textIndex; i++) {
buf.append(text.charAt(i));
}
buf.append(replacementList[replaceIndex]);
start = textIndex + searchList[replaceIndex].length();
textIndex = -1;
replaceIndex = -1;
tempIndex = -1;
// find the next earliest match
// NOTE: logic mostly duplicated above START
for (int i = 0; i < searchLength; i++) {
if (noMoreMatchesForReplIndex[i] || searchList[i] == null ||
searchList[i].isEmpty() || replacementList[i] == null) {
continue;
}
tempIndex = text.indexOf(searchList[i], start);
// see if we need to keep searching for this
if (tempIndex == -1) {
noMoreMatchesForReplIndex[i] = true;
} else {
if (textIndex == -1 || tempIndex < textIndex) {
textIndex = tempIndex;
replaceIndex = i;
}
}
}
// NOTE: logic duplicated above END
}
final int textLength = text.length();
for (int i = start; i < textLength; i++) {
buf.append(text.charAt(i));
}
final String result
= buf.
toString(); if (!repeat) {
return result;
}
return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
}
}
private static class BoannSolution implements Solution {
return "Boann";
}
if (search.length != replace.length) {
}
StringBuilder sb = new StringBuilder();
outer:
for (int i = 0; i < subject.length(); i++) {
for (int j = 0; j < search.length; j++) {
if (subject.regionMatches(i, find, 0, find.length())) {
sb.append(replace[j]);
i += find.length() - 1;
continue outer;
}
}
sb.append(subject.charAt(i));
}
return sb.toString();
}
}
}