Skip to main content
Code Review

Return to Answer

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

edit: I have posted a question here here with an improved solution, do check it out too.

TRAITS is a similar take on the array-based lookup approach suggested in @Antot @Antot's answer, except that it is String[][]-based and contains the "X" values for easier lookups when the percentage is exactly 50%, i.e. THRESHOLD. BLANK represents a Map where there are no responses for a given choice.

edit: I have posted a question here with an improved solution, do check it out too.

TRAITS is a similar take on the array-based lookup approach suggested in @Antot's answer, except that it is String[][]-based and contains the "X" values for easier lookups when the percentage is exactly 50%, i.e. THRESHOLD. BLANK represents a Map where there are no responses for a given choice.

edit: I have posted a question here with an improved solution, do check it out too.

TRAITS is a similar take on the array-based lookup approach suggested in @Antot's answer, except that it is String[][]-based and contains the "X" values for easier lookups when the percentage is exactly 50%, i.e. THRESHOLD. BLANK represents a Map where there are no responses for a given choice.

added 189 characters in body
Source Link
h.j.k.
  • 19.3k
  • 3
  • 37
  • 93

edit: I have posted a question here with an improved solution, do check it out too.

edit: I have posted a question here with an improved solution, do check it out too.

Source Link
h.j.k.
  • 19.3k
  • 3
  • 37
  • 93

Returning results from methods

Most of your processing methods except for computePersonality() mutate the method arguments as a way of passing the results back to the method caller. There is nothing inherently wrong with this approach, but it does mean you have to be careful with the ordering of your method arguments (since your results are just int[] arrays), and you will not be able to chain method calls together.

In some cases, it might be better to use a payload class, even if it's a custom one, to better model a result from a method. For example, instead of passing around two int[] arrays, you can have a Result class that provide methods like updateA(int[]) or computePercentage() to better encapsulate such functionality.

Enumerating and validating inputs

Since your processing is only done on A or B (case-insensitive) values, an enum may be a suitable option of representing these choices:

enum Choice {
 A, B, UNKNOWN;
 public static Choice of(char x) {
 try {
 return Choice.valueOf(String.valueOf(x).toUpperCase());
 } catch (IllegalArgumentException e) {
 return Choice.UNKNOWN;
 }
 }
}

In this case, we 'convert' "A"/"a" as Choice.A, and "B"/"b" as Choice.B. Other values are simply represented as Choice.UNKNOWN.

Computing and grouping scores

Based on my interpretation of your countsOfAB() method, the computation seems like:

  1. Divide the 70 responses into 10 chunks of 7 responses each:

     ABABABaABABABaABABABa...ABABABa
     <- Q -><- R -><- S ->...<- Z ->
     ABABABa: chunk Q
     ABABABa: chunk R
     ABABABa: chunk S
     ...
     ABABABa: chunk Z
    
  2. From each chunk, the 1st response goes into its own grouping (i.e. the \0ドル^{th}\$ element of your int[] countsA/B array), the 2nd and 3rd responses goes into the next, and finally the last two into the 4th grouping:

$$ A \rbrace 0 \\ \left.\begin{matrix} B \\ A \\ \end{matrix}\right\rbrace 1 \\ \left.\begin{matrix} B \\ A \\ \end{matrix}\right\rbrace 2 \\ \left.\begin{matrix} B \\ a \\ \end{matrix}\right\rbrace 3 $$

  1. Collate all the groupings across the 10 chunks, so that for the example, we have the following representations:

$$\begin{array}{|c|c|c|} \hline Grouping & Count for A & Count for B\\ \hline 0 & 10 & 0 \\ \hline 1 & 10 & 10 \\ \hline 2 & 10 & 10 \\ \hline 3 & 10 & 10 \\ \hline \end{array} $$

With a bit of math, it is not hard to combine these into a single step, especially when you involve Java 8's stream:

private static final int GROUPING = 7;
private static Map<Choice, Map<Integer, Long>> compute(String input) {
 return join(IntStream.range(0, input.length()).mapToObj(i -> map(input, i)))
 .collect(Collectors.groupingBy(Entry::getKey,
 Collectors.groupingBy(Entry::getValue, Collectors.counting())));
}
private static Map<Choice, Integer> map(String input, int i) {
 return Collections.singletonMap(Choice.of(input.charAt(i)), ((i % GROUPING) + 1) / 2);
}
private static <K, V> Stream<Entry<K, V>> join(Stream<Map<K, V>> stream) {
 return stream.map(Map::entrySet).flatMap(Set::stream);
}

join() is a simple utility method that converts a Stream of Map elements into a Stream of their Entry contents.

The magic lies in the map(String, int) method, which creates a Choice \$\to\$ grouping mapping by the formula ((i % GROUPING) + 1) / 2 (which is implicitly truncated to an int):

$$\begin{array}{|c|c|} \hline i & f(i)\\ \hline 0 & 0 \\ \hline 1 & 1 \\ \hline 2 & 1 \\ \hline 3 & 2 \\ \hline 4 & 2 \\ \hline 5 & 3 \\ \hline 6 & 3 \\ \hline 7 & 0 \\ \hline 8 & 1 \\ \hline 9 & 1 \\ \hline \cdots & \cdots \\ \hline 67 & 2 \\ \hline 68 & 3 \\ \hline 69 & 3 \\ \hline \end{array} $$

In the compute() method, we create these mappings per response, join() them together, and then return the desired Map<Choice, Map<Integer, Long>> object groupingBy() the Choice key (using Entry::getKey as a method reference), and counting() the values (Entry::getValue) after groupingBy() them as well.

The Map representation for the example is thus:

{B={1=10, 2=10, 3=10}, A={0=10, 1=10, 2=10, 3=10}}

There is no 0=... result for choice B, since all the responses in that grouping are A.

Calculating the scores and mapping

Next, we need to calculate the percentages of B responses per group, i.e. we will have 0% for group 0 and 50% for the rest for the example.

From the Map<Choice, Map<Integer, Long>> result we have previously, it is again not hard to apply Java 8's stream processing again to perform the calculation and do the mapping.

Let's introduce some constants first:

private static final String[][] TRAITS = Stream.of("EXI", "SXN", "TXF", "JXP")
 .map(v -> v.split(""))
 .toArray(String[][]::new);
private static final Map<Integer, Long> BLANK =
 Collections.unmodifiableMap(IntStream.range(0, TRAITS.length).boxed()
 .collect(Collectors.toMap(i -> i, i -> 0L)));
private static final int THRESHOLD = 50;

TRAITS is a similar take on the array-based lookup approach suggested in @Antot's answer, except that it is String[][]-based and contains the "X" values for easier lookups when the percentage is exactly 50%, i.e. THRESHOLD. BLANK represents a Map where there are no responses for a given choice.

public static String getPersonality(String input) {
 Map<Choice, Map<Integer, Long>> map = compute(Objects.requireNonNull(input));
 return join(Stream.of(Choice.A, Choice.B).map(v -> map.getOrDefault(v, BLANK)))
 .collect(Collectors.groupingBy(Entry::getKey,
 Collectors.summingDouble(Entry::getValue)))
 .entrySet()
 .stream()
 .map(entry -> derive(map.getOrDefault(Choice.B, BLANK), entry))
 .collect(Collectors.joining());
}
private static String derive(Map<Integer, Long> m, Entry<Integer, Double> i) {
 return TRAITS[i.getKey()][index(m.getOrDefault(i.getKey(), 0L), i.getValue())];
}
private static int index(long numerator, double denominator) {
 int value = (int) Math.round(100 * numerator / denominator);
 return value < THRESHOLD ? 0 : value == THRESHOLD ? 1 : 2;
}

Since we are only interested in A and B responses, we query for the map results via Stream.of(Choice.A, Choice.B).map(v -> map.getOrDefault(v, BLANK)). Using BLANK as a default value here is crucial to provide 0s for the calculations later.

At the end of the first collect() step, what we have is a Map<Integer, Double> representing the summation of responses per grouping, by applying groupingBy() on the key and summingDouble() on the values.

For each of the entries (groupings \0ドル\ldots3\$), we can now derive the personality trait with the method derive(). Its first argument is the results of B responses from the compute() method, and the second argument is the Map entry of grouping \$\to\$ total responses of the grouping.

From this entry, the key (grouping) is used for the first dimension of our TRAITS array, and the second dimension is the result of computing our 'B-percentage' in the index() method.

Finally, each resulting String from the derive() method is joined together to give the four-letter personality by using Collectors.joining().

Conclusion

This is quite a lengthy answer, but I hope it adequately describes how the various steps in your solution can be re-imagined and re-composed into what are essentially two stream-based operations (helper methods aside).

lang-java

AltStyle によって変換されたページ (->オリジナル) /