Search code examples
javajava-8java-stream

Finding the value from nested arrays with the max average value using streams in Java 8


I got this as a coding interview question. I was able to get the desired results and pass all the test cases. However I did not clear the interview round. I think it was because I did it the imperative style - multiple for loops, multiple HashMaps, etc. I am trying to achieve the same results using streams. Here is the problem:

Given a array String[][] ex: {{"Scott","85"},{"Scott","67"},{"Tiger","81"},{"Tiger","21"},{"Marissa","92"}}, find the person with highest average score. Below is the code snippet of as far as I could get.

public static void main(String[] args) {
    String[][] scores = {{"Tee","63"},{"Tee","75"},{"Tee","84"},{"Dee","59"},{"Dee","77"},{"Dee","66"},{"Dee","71"},{"Lee","81"},{"Lee","78"},{"Lee","58"},{"Bree","98"},{"Bree","78"}};

    List<List<String>> slist = Arrays.stream(scores)
            .map(Arrays::asList)
            .collect(Collectors.toList());
    
    slist.stream().collect(Collectors.groupingBy(l->l.get(0)));
}

I am able to convert the String[][] to List<List<String>>. I am looking to stream this new list, group it by the 0th element, then sum it and then get the average.


Solution

  • This is one way.

    String[][] scores = {{"Tee","63"},{"Tee","75"},{"Tee","84"},{"Dee","59"}, 
       {"Dee","77"},{"Dee","66"},{"Dee","71"},{"Lee","81"},{"Lee","78"},{"Lee","58"}, 
       {"Bree","98"},{"Bree","78"}};
    
    • Stream the array to a stream of individual arrays.
    • Collect to a Map<String, Double> for name and average.
    • The average is found by using Collectors.groupingBy and employing a Collectors.averagingDouble to compute the average of each value. This last collector is told which value to average by the ToDoubleFunction which in this case must do a double or integer parsing of the second value of each array in the stream.
    • Then stream the entries and use the maxBy collector comparing the Entry values for the highest.
    • Since maxBy returns an optional it is retrieved by first checking if a value is present (the source array might be empty), then using get.
    Optional<Entry<String, Double>> max = Arrays.stream(scores)
            .collect(Collectors.groupingBy(a->a[0],
                    Collectors.averagingDouble(a->Double.parseDouble(a[1]))))    
                    .entrySet()
                    .stream()
                    .collect(Collectors.maxBy(Entry.comparingByValue()));
    
    if (max.isPresent()) {
        Entry<String,Double> result = max.get();
        System.out.printf("%s has the highest average of %.2f%n",
                 result.getKey(),result.getValue());
    } else {
        System.out.println("No maximum found");
    }
    
    

    Prints

    Bree has the highest average of 88.00
    

    Note that I did this in by immediately streaming the entries and finding the maximum average. It could also have been done in two separate steps. First computing and saving a map of averages of all individuals. Then streaming the entries and finding the maximum.

    Finally, one could also put in some filters right after streaming the source array to check for empty arrays, null values, etc. to avoid exceptions being thrown later in the pipeline.