Could you help me further improve this code, which includes some feedback from this site already Find common “characters” in 2 given strings (rev4)?
What has changed since version 4:
I re-implemented the short-circuiting of scanning the longest string argument via limit(), as suggested by @mjolka. I think that part is quite elegant.
Why I'd like a review:
I would like to explore the possibility of .collect()ing to a String in one go, without losing efficiency.
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class CommonCharacters5 {
private static String commonCharactersOf(String string1, String string2) {
// Requirement
//
// Always return lowercase versions of common characters. e.g.:
//
// OK: (a, a) -> a; OK: (a, A) -> a; OK: (A, A) -> a
// No: (a, A) -> a; No: (A, A) -> A; No: (aA, aA) -> aA;
//
// Requirement
//
// Return common characters joined in a String, preserving the order in
// which they appeared in the longest argument, or in the first argument if
// the arguments are of the same length.
//
// Requirement
//
// Handle "characters" (i.e. code points) outside the Basic Multilingual
// Plane (BMP), including characters from Supplementary Planes.
// There should be no `char' or `Character' based "false positives". e.g.:
//
// String string1 = "\uD835\uDC00", string2 = "\uD835\uDC01";
// string1 and string2 share no characters in the intended acceptation of
// "character".
String shorterArgument, longerArgument;
if (string1.length() < string2.length()) {
shorterArgument = string1;
longerArgument = string2;
} else {
shorterArgument = string2;
longerArgument = string1;
}
// @formatter:off
Set<Integer> shorterArgumentCodePoints =
shorterArgument.codePoints()
.map(Character::toLowerCase)
.boxed()
.collect(Collectors.toSet());
StringBuilder stringBuilder = new StringBuilder();
longerArgument.codePoints()
.map(Character::toLowerCase)
.distinct()
.filter(shorterArgumentCodePoints::contains)
.limit(shorterArgumentCodePoints.size())
.forEach(stringBuilder::appendCodePoint);
return stringBuilder.toString();
}
@Parameters(name = "({0}, {1}) -> {2}")
public static Collection<String[]> data() {
return Arrays.asList(new String[][] {
// @formatter:off
{ "" , "" , "" },
{ "a" , "" , "" },
{ "" , "a" , "" },
{ "aa" , "" , "" },
{ "" , "aa" , "" },
{ "a" , "a" , "a" },
{ "aa" , "b" , "" },
{ "b" , "aa" , "" },
{ "ab" , "ba" , "ab" },
{ "aba" , "ab" , "ab" },
{ "aba" , "ba" , "ab" },
{ "aba" , "aab" , "ab" },
{ "a" , "A" , "a" },
{ "A" , "a" , "a" },
{ "A" , "A" , "a" },
{ "ab" , "AB" , "ab" },
{ "AB" , "ab" , "ab" },
{ "aB" , "Ab" , "ab" },
{ "aB" , "Ba" , "ab" },
{ "aB" , "Ba" , "ab" },
{ "abc" , "ac" , "ac" },
{ "abc" , "ca" , "ac" },
{ "abc" , "cba" , "abc" },
{ "a" , "\uD835\uDC1A" , "" },
{ "\uD835\uDC1A" , "\uD835\uDC1A" , "\uD835\uDC1A" },
{ "\uD835\uDC00" , "\uD835\uDC00" , "\uD835\uDC00" },
{ "\uD835\uDC1A" , "\uD835\uDC00" , "" },
{ "\uD835\uDC00" , "\uD835\uDC01" , "" },
{ "\uD801\uDC2B" , "\uD801\uDC2B" , "\uD801\uDC2B" },
{ "\uD801\uDC03" , "\uD801\uDC03" , "\uD801\uDC2B" },
{ "\uD801\uDC2B" , "\uD801\uDC03" , "\uD801\uDC2B" },
{ "\uD83D\uDE80" , "\uD83D\uDE80" , "\uD83D\uDE80" },
{ "a" , "aaaaaaaaaaaaaaaaa" , "a" },
// The last test should still work, and work fast, with a second
// argument string starting with "a" and ending _many_ characters later
// The last test values doe not test it, but illustrate the scenario
// @formatter:on
});
}
private String string1;
private String string2;
private String expected;
public CommonCharacters5(String string1, String string2, String expected) {
this.string1 = string1;
this.string2 = string2;
this.expected = expected;
}
@org.junit.Test
public void test() {
Assert.assertEquals(expected, commonCharactersOf(string1, string2));
}
}
JUnit test results screenshot, useful to glance at what the hexadecimal literals encode, in terms of surrogate pairs: