fork download
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.Iterator;
  4. import java.util.List;
  5. import java.util.Set;
  6. import java.util.function.Function;
  7. import java.util.stream.Collectors;
  8. class CommonCharacters {
  9. @SuppressWarnings("boxing")
  10. private static String commonCharactersOf(String string1, String string2) {
  11. // Requirement
  12. //
  13. // Always return lowercase versions of common characters. e.g.:
  14. //
  15. // OK: (a, a) -> a
  16. // OK: (a, A) -> a
  17. // OK: (A, A) -> a
  18. // No: (A, A) -> A
  19. // No: (aA, aA) -> aA
  20. //
  21. // Requirement
  22. //
  23. // Return common characters joined in a String, preserving the order in
  24. // which they appeared in the longest argument, or in the first argument if
  25. // the arguments are of the same length.
  26. //
  27. // Requirement
  28. //
  29. // Handle "characters" (i.e. code points) outside the Basic Multilingual
  30. // Plane (BMP), including characters from Supplementary Planes.
  31. // There should be no `char' or `Character' based "false positives". i.e.:
  32. //
  33. // String string1 = "\uD835\uDC00", string2 = "\uD835\uDC01";
  34. //
  35. // string1 and string2 share no characters in the intended acceptation of
  36. // "character".
  37. String shortestArgument, longestArgument;
  38. if (string1.length() < string2.length()) {
  39. shortestArgument = string1;
  40. longestArgument = string2;
  41. } else {
  42. shortestArgument = string2;
  43. longestArgument = string1;
  44. }
  45. // @formatter:off
  46. Set<Integer> codePointsSeen =
  47. shortestArgument.codePoints()
  48. .boxed()
  49. .map(Character::toLowerCase)
  50. .collect(Collectors.toSet());
  51. List<Integer> codePointsInCommon = new ArrayList<>();
  52. for (Iterator<Integer> iterator = longestArgument.codePoints()
  53. .distinct()
  54. .iterator();
  55. iterator.hasNext() &&
  56. codePointsInCommon.size() < codePointsSeen.size();) {
  57. // @formatter:on
  58. Integer codePoint = iterator.next();
  59. int lowerCaseCodePoint = Character.toLowerCase(codePoint);
  60. if (codePointsSeen.contains(lowerCaseCodePoint)) {
  61. codePointsInCommon.add(lowerCaseCodePoint);
  62. }
  63. }
  64. StringBuilder stringBuilder = new StringBuilder();
  65. for (Integer codePoint : codePointsInCommon) {
  66. stringBuilder.appendCodePoint(codePoint);
  67. }
  68. return stringBuilder.toString();
  69. }
  70. @SuppressWarnings("boxing")
  71. public static void main(String[] args) {
  72. // @formatter:off
  73. String[][] testArgumentsAndExpectedValues = {
  74. { "" , "" , "" },
  75. { "a" , "" , "" },
  76. { "" , "a" , "" },
  77. { "aa" , "" , "" },
  78. { "" , "aa" , "" },
  79. { "a" , "a" , "a" },
  80. { "aa" , "b" , "" },
  81. { "b" , "aa" , "" },
  82. { "ab" , "ba" , "ab" },
  83. { "aba" , "ab" , "ab" },
  84. { "aba" , "ba" , "ab" },
  85. { "aba" , "aab" , "ab" },
  86. { "a" , "A" , "a" },
  87. { "A" , "a" , "a" },
  88. { "A" , "A" , "a" },
  89. { "ab" , "AB" , "ab" },
  90. { "AB" , "ab" , "ab" },
  91. { "aB" , "Ab" , "ab" },
  92. { "aB" , "Ba" , "ab" },
  93. { "aB" , "Ba" , "ab" },
  94. { "a" , "\uD835\uDC1A" , "" },
  95. { "\uD835\uDC1A" , "\uD835\uDC1A" , "\uD835\uDC1A" },
  96. { "\uD835\uDC00" , "\uD835\uDC00" , "\uD835\uDC00" },
  97. { "\uD835\uDC1A" , "\uD835\uDC00" , "" },
  98. { "\uD835\uDC00" , "\uD835\uDC01" , "" },
  99. { "\uD801\uDC2B" , "\uD801\uDC2B" , "\uD801\uDC2B" },
  100. { "\uD801\uDC03" , "\uD801\uDC03" , "\uD801\uDC2B" },
  101. { "\uD801\uDC2B" , "\uD801\uDC03" , "\uD801\uDC2B" },
  102. { "\uD83D\uDE80" , "\uD83D\uDE80" , "\uD83D\uDE80" },
  103. { "a" , "aaaaaaaaaaaaaaaaa" , "a" },
  104. // The last test should still work, and work fast, with a second
  105. // argument string starting with "a" and ending _many_ characters later
  106. // The last test values doe not test it, but illustrate the scenario
  107. };
  108. int maximumTestArgumentLength =
  109. Arrays.stream(testArgumentsAndExpectedValues)
  110. .flatMap(testValues -> Arrays.stream(testValues)
  111. .limit(2))
  112. .mapToInt(String::length)
  113. .max()
  114. .getAsInt();
  115. // @formatter:on
  116. int maximumQuotedTestArgumentLength = maximumTestArgumentLength + 2;
  117. for (int i = 0; i < testArgumentsAndExpectedValues.length; i++) {
  118. String[] currentTestValues = testArgumentsAndExpectedValues[i];
  119. String string1 = currentTestValues[0];
  120. String string2 = currentTestValues[1];
  121. String expectedResult = currentTestValues[2];
  122. String actualResult = commonCharactersOf(string1, string2);
  123. boolean testSuccessful = expectedResult.equals(actualResult);
  124. if (testSuccessful) {
  125. // continue; // TODO: uncomment to filter out successful tests
  126. }
  127. Function<String, String> quoteString = s -> '"' + s + '"';
  128. // @formatter:off
  129. String outputFormat = "%2d) "
  130. + "%5s: "
  131. + "(%-" + maximumQuotedTestArgumentLength + "s"
  132. + " , "
  133. + "%-" + maximumQuotedTestArgumentLength + "s)"
  134. + " -> "
  135. + "%s "
  136. + "(%s)"
  137. + "%n";
  138. System.out.printf(outputFormat,
  139. i,
  140. testSuccessful ? "Success" : "Failure",
  141. quoteString.apply(string1),
  142. quoteString.apply(string2),
  143. quoteString.apply(actualResult),
  144. quoteString.apply(expectedResult));
  145. // @formatter:on
  146. }
  147. }
  148. }
  149.  
  150.  
Success #stdin #stdout 0.25s 321152KB
stdin
Standard input is empty
stdout
 0) Success: (""                  , ""                 ) -> "" ("")
 1) Success: ("a"                 , ""                 ) -> "" ("")
 2) Success: (""                  , "a"                ) -> "" ("")
 3) Success: ("aa"                , ""                 ) -> "" ("")
 4) Success: (""                  , "aa"               ) -> "" ("")
 5) Success: ("a"                 , "a"                ) -> "a" ("a")
 6) Success: ("aa"                , "b"                ) -> "" ("")
 7) Success: ("b"                 , "aa"               ) -> "" ("")
 8) Success: ("ab"                , "ba"               ) -> "ab" ("ab")
 9) Success: ("aba"               , "ab"               ) -> "ab" ("ab")
10) Success: ("aba"               , "ba"               ) -> "ab" ("ab")
11) Success: ("aba"               , "aab"              ) -> "ab" ("ab")
12) Success: ("a"                 , "A"                ) -> "a" ("a")
13) Success: ("A"                 , "a"                ) -> "a" ("a")
14) Success: ("A"                 , "A"                ) -> "a" ("a")
15) Success: ("ab"                , "AB"               ) -> "ab" ("ab")
16) Success: ("AB"                , "ab"               ) -> "ab" ("ab")
17) Success: ("aB"                , "Ab"               ) -> "ab" ("ab")
18) Success: ("aB"                , "Ba"               ) -> "ab" ("ab")
19) Success: ("aB"                , "Ba"               ) -> "ab" ("ab")
20) Success: ("a"                 , "𝐚"               ) -> "" ("")
21) Success: ("𝐚"                , "𝐚"               ) -> "𝐚" ("𝐚")
22) Success: ("𝐀"                , "𝐀"               ) -> "𝐀" ("𝐀")
23) Success: ("𝐚"                , "𝐀"               ) -> "" ("")
24) Success: ("𝐀"                , "𝐁"               ) -> "" ("")
25) Success: ("𐐫"                , "𐐫"               ) -> "𐐫" ("𐐫")
26) Success: ("𐐃"                , "𐐃"               ) -> "𐐫" ("𐐫")
27) Success: ("𐐫"                , "𐐃"               ) -> "𐐫" ("𐐫")
28) Success: ("🚀"                , "🚀"               ) -> "🚀" ("🚀")
29) Success: ("a"                 , "aaaaaaaaaaaaaaaaa") -> "a" ("a")