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)
};
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.
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