[ Exercises | Chapter Index | Main Index ]

## Solution for Programming Exercise 10.5

This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.

### Exercise 10.5:

This is a short exercise in using the stream API. Suppose that the class Score is defined as

    /**
     * Data for one student about a score on a test.
     */
    private record ScoreInfo(
        String lastName,
        String firstName,
        int score
    ) { };

defined here as a record class for convenience (see Section 7.4). And suppose that scoreData is an array of ScoreInfos containing information about the scores of students on a test. Use the stream API to do each of the following tasks:

- print the number of students (without using scoreData.length)
- print the average score for all of the students
- print the number of students who got an A (score greater than or equal to 90)
- use the collect() stream operation to create a List<String> that contains the names of students whose score was less than 70; the names should be in the form first name followed by last name
- print the names from the List that was generated in the previous task
- print out the students' names and scores, ordered by last name
- print out the students' names and scores, ordered by score

You can put all of the code in main() routine and include ScoreInfo as a nested class. Do not use any for loops or other control structures. Do everything using the stream API. For testing your code, you can use this array:

    private static ScoreInfo[] scoreData = new ScoreInfo[] {
            new ScoreInfo("Smith","John",70),
            new ScoreInfo("Doe","Mary",85),
            new ScoreInfo("Page","Alice",82),
            new ScoreInfo("Cooper","Jill",97),
            new ScoreInfo("Flintstone","Fred",66),
            new ScoreInfo("Rubble","Barney",80),
            new ScoreInfo("Smith","Judy",48),
            new ScoreInfo("Dean","James",90),
            new ScoreInfo("Russ","Joe",55),
            new ScoreInfo("Wolfe","Bill",73),
            new ScoreInfo("Dart","Mary",54),
            new ScoreInfo("Rogers","Chris",78),
            new ScoreInfo("Toole","Pat",51),
            new ScoreInfo("Khan","Omar",93),
            new ScoreInfo("Smith","Ann",95)
    };

Discussion

In each case, to process the values from the array using the stream API, we need to make a stream containing those values. That's done by calling Arrays.stream(scoreData), except in the case of printing out the names in a List, where list.stream() can be used. From there, we just need to apply the appropriate stream operators to get the desired results. I have added some comments on each of the assigned tasks to the program.

The Solution

    import java.util.List;
    import java.util.Arrays;
    import java.util.stream.Collectors;

    /**
     * This program tests the stream API by doing various computations.
     */
    public class StreamTest {

        /**
         * Data for one student about a score on a test.
         */
        private record ScoreInfo(
            String lastName,
            String firstName,
            int score
        ) { };

        /**
         * A test array of StudentInfo that will be used to create streams.
         */
        private static ScoreInfo[] scoreData = new ScoreInfo[] {
                new ScoreInfo("Smith","John",70),
                new ScoreInfo("Doe","Mary",85),
                new ScoreInfo("Page","Alice",82),
                new ScoreInfo("Cooper","Jill",97),
                new ScoreInfo("Flintstone","Fred",66),
                new ScoreInfo("Rubble","Barney",80),
                new ScoreInfo("Smith","Judy",48),
                new ScoreInfo("Dean","James",90),
                new ScoreInfo("Russ","Joe",55),
                new ScoreInfo("Wolfe","Bill",73),
                new ScoreInfo("Dart","Mary",54),
                new ScoreInfo("Rogers","Chris",78),
                new ScoreInfo("Toole","Pat",51),
                new ScoreInfo("Khan","Omar",93),
                new ScoreInfo("Smith","Ann",95)
        };

        public static void main(String[] args) {

            /* The number of students can be computed simply by applying
             * the .count() operation to a stream created from the array.
             * This is more than a little silly, since the answer will
             * be the same as scoreData.length, but it sets up the
             * general format for the stream operations in the rest of
             * the program. */

            long count = Arrays.stream(scoreData).parallel().count();
            System.out.println("Number of students: " + count);

            /* To get the average, map the stream of ScoreData objects
             * to an IntStream containing just the scores, and then use
             * the .average() operation from the IntStream class. Since
             * .average() returns an Optional<Double>, we need to
             * use getAsDouble() to retrieve the final answer. */

            double avg = Arrays.stream(scoreData).parallel()
                                  .mapToInt( s -> s.score )
                                  .average()
                                  .getAsDouble();
            System.out.printf("Average grade:  %1.2f%n", avg );

            /* To get the number of students who got an A, first filter the
             * stream letting through only students who satisfy the
             * predicate  s -> s.score >= 90.  This predicate returns true
             * only for students whose score is greater than or equal to 90.
             * The number is then computed by applying .count() to the
             * resulting stream. */

            long countA = Arrays.stream(scoreData).parallel()
                       .filter( s -> s.score >= 90 )
                       .count();
            System.out.println("Number who got an A: " + countA);
            System.out.println();

            /* To get a list of names of students who failed, first filter
             * the stream to let through only ScoreInfo objects with score
             * less than 70.  Since we want the names of students, we need
             * to convert the stream of ScoreInfo objects into a stream
             * of strings by applying a mapping operation.  Then applying
             * .collect(Collectors.toList()) produces the List that we need. */

            List<String> failing = Arrays.stream(scoreData)
                                              .filter( s -> (s.score < 70) )
                                              .map( s -> (s.firstName + " " + s.lastName) )
                                              .collect( Collectors.toList() );

            /* Now we need to print the list, which can easily be done with
             * a for-each loop.  However, it can also be done using .forEach()
             * operation on a stream.  In this case, the String Consumer in the
             * .forEach() operation is given as the method reference
             * System.out::println.  That is, it is the println() method in the
             * object System.out.  This method reference is the same as writing
             * the lambda expression  s -> System.out.println(s).  */

            System.out.println("Failing students: ");
            failing.stream().forEach( System.out::println );
            System.out.println();

            /* To print the data ordered by last name, apply the .sorted() operation
             * to sort the values in the stream by last name, then use .forEach()
             * to output the information from each ScoreInfo object in the stream.
             * The .sorted() operation takes a parameter that is a Comparator for
             * comparing two values from the stream.  In this case, we just
             * compare the lastNames from two ScoreInfo objects, using the standard
             * compareTo() method for Strings. */

            System.out.println("Data sorted by last name:");
            Arrays.stream(scoreData)
                 .sorted( (s1,s2) -> s1.lastName.compareTo(s2.lastName) )
                 .forEach( s -> System.out.printf(
                                     "  %s, %s: %d%n", s.lastName, s.firstName, s.score) );
            System.out.println();

            /* The data can be output ordered by score in the same way, using a
             * different comparator in the .sorted() operation. */

            System.out.println("Data sorted by score:");
            Arrays.stream(scoreData)
                 .sorted( (s1,s2) -> s1.score - s2.score )
                 .forEach( s -> System.out.printf(
                                     "  %s, %s: %d%n", s.lastName, s.firstName, s.score) );

        }

    } // end StreamTest

[ Exercises | Chapter Index | Main Index ]