Prelude Data.Char> let flip c = if (isUpper c) then (toLower c) else (toUpper c) Prelude Data.Char> map flip "This is a TEST" "tHIS IS A test"
public class StringDemo1 {
public static String invertCapitals(String other) {
return other.chars()
.mapToObj(StringDemo1::flipCap)
.map(c -> Character.toString(c))
.reduce("", (s, c) -> s + c);
}
public static Character flipCap(int c) {
if (c >= 'A' && c <= 'Z') {
return (char)(c - 'A' + 'a');
} else if (c >= 'a' && c <= 'z') {
return (char)(c - 'a' + 'A');
} else {
return (char)c;
}
}
}
First, we need to convert the string to a stream. That is what the
chars() method does. Unfortunately, it creates an IntStream. We use the mapToObj() method to turn the IntStream into a Stream<Character>. Having done this, we use map() to turn it into a Stream<String>, and finally we can use reduce() to combine it all into a single string. While this does get the job done, it is very inefficient, as a new String object must be allocated for each reduction. The following variation uses
collect() to use a StringBuilder to accumulate the new String efficiently:public class StringDemo2 {
public static String invertCapitals(String other) {
return other.chars()
.mapToObj(StringDemo1::flipCap)
.map(c -> Character.toString(c))
.collect(StringBuilder::new,StringBuilder::append,StringBuilder::append)
.toString();
}
}
Using
collect() is arguably not as aesthetically pleasing as reduce(). Here is an explanation of the arguments:- The first argument generates the collection that will be the accumulation target.
- The second argument appends an element to the collection.
- The third argument joins two collections.
This particular example is odd because StringBuilder::append is an overloaded static method. The first one appends a String; the second one appends a CharSequence, an interface that StringBuilder implements.
Having compared the aesthetics, what about performance?
I found that the version with collect() could process a 100,000 character string in 19 milliseconds, while the version with reduce() requires 6520 milliseconds.
My test program is below. It provides a nice demonstration of passing functions as parameter values.
My test program is below. It provides a nice demonstration of passing functions as parameter values.
import java.util.function.Function;
import java.util.stream.IntStream;
public class StringDemoComparison {
public static void main(String[] args) {
String input =
IntStream.iterate(1, x -> 1 + x)
.mapToObj(x -> Character.toString((char)(x % 58 + 65)))
.limit(100000)
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
.toString();
runDemo(StringDemo1::invertCapitals, input);
runDemo(StringDemo2::invertCapitals, input);
} public static void runDemo(Functionfunc, String input) { long start = System.currentTimeMillis(); String result = func.apply(input); long duration = System.currentTimeMillis() - start; System.out.println(result.length()); System.out.println("Duration for: " + func.toString() + " is: " + duration); } }
No comments:
Post a Comment