diff --git a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java new file mode 100644 index 0000000000000000000000000000000000000000..252404812983f917e134f991120575d7eb38e6cd --- /dev/null +++ b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/CsvCollector.java @@ -0,0 +1,72 @@ +package de.hdm_stuttgart.mi.javastreams; + +import java.util.EnumSet; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +/** + * Turning a list of strings into a character separated list of words. Example: + * + * List {"Fred", "Eve", "Petra" } and separator "+" results + * in the string "Fred+Eve+Petra". + * + */ +public class CsvCollector implements Collector<String, StringBuffer, String> { + + final String separator; + + /** + * Providing the list's desired separator string. + * @param separator String filling the gap between two adjacent strings. + */ + public CsvCollector(final String separator) { + this.separator = separator; + } + + @Override + public Supplier<StringBuffer> supplier() { + return () -> new StringBuffer(); + } + + @Override + public BiConsumer<StringBuffer, String> accumulator() { + return (csv, s) -> { + if (0 != csv.length()) { // Buffer non-empty? + csv.append(separator); // ==> Append separator ... + } + csv.append(s); // and string. + }; + } + + @Override + public BinaryOperator<StringBuffer> combiner() { + return (csv1, csv2) -> { + if (0 == csv1.length()) { // Left list empty? + return csv2; // ==> return right list. + + } else if (0 == csv2.length()) { // Right List empty? + return csv1; // ==> return left list. + + } else { // Both lists non-empty? + return csv1. // ==> Concatenate both lists. + append(separator). + append(csv2); + } + }; + } + + @Override + public Function<StringBuffer, String> finisher() { + return csv -> csv.toString(); + } + + @Override + public Set<java.util.stream.Collector.Characteristics> characteristics() { + return EnumSet.of(Characteristics.UNORDERED); + } + +} diff --git a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java index 8e3a64d9f0b73c41df643479a1fa84a66591fb8c..0b506d7c818ec9b8ab35ddb95980b3db08eed12f 100644 --- a/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java +++ b/P/Sda1/Streams/Solution/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java @@ -34,8 +34,10 @@ package de.hdm_stuttgart.mi.javastreams; import java.util.Arrays; import java.util.List; +/** Representing students among with examination marks. */ public class Student { + /** @return Test sample of students. */ public static List<Student> createRoster() { // Create test data records final Student[] roster = new Student[] { @@ -49,12 +51,18 @@ public class Student { return Arrays.asList(roster); } + /** Male or female */ public enum Sex { - MALE("male"), FEMALE("female"); + /** */ + MALE("male"), + /** */ + FEMALE("female"); final String extern; private Sex(final String extern) {this.extern = extern;} + + @Override public String toString(){return extern;} } @@ -71,22 +79,34 @@ public class Student { this.emailAddress = email; } + /** * @return An examination's result */ public int getMark() { return mark; } - public Sex getGender() { + + /** @return A student's sex. */ + public Sex getSex() { return gender; } + /** @return A student's name. */ public String getName() { return name; } + /** @return A student's e-mail. */ public String getEmailAddress() { return emailAddress; } - public static int compareByMark(Student a, Student b) { + /** + * Compare two students by their marks + * @param a first student + * @param b second student + * @return positive if student b's mark is better than student a's, + * zero if marks are equal, negative if ... + */ + public static int compareByMark(final Student a, final Student b) { return a.mark - b.mark; } @@ -94,9 +114,4 @@ public class Student { public String toString() { return getName() + "(" + gender + ", " + getEmailAddress() + ", mark=" + getMark() + ")"; } - - public void print() { - System.out.println(this); - } - } \ No newline at end of file diff --git a/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java b/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java index f6b78ef7915a904822f4cb0af6740458f9853b9e..39a44d2584aaa1b6ceb173b7357e2f0726da18a8 100644 --- a/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java +++ b/P/Sda1/Streams/Solution/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java @@ -2,9 +2,11 @@ package de.hdm_stuttgart.mi.javastreams; import static org.hamcrest.MatcherAssert.assertThat; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.OptionalDouble; +import java.util.stream.Collector; import java.util.stream.Collectors; import org.hamcrest.Matchers; @@ -40,8 +42,7 @@ public class Java8FunctionalTest { public void allMaleDistinctNameOrderedByEmail() { final List<String> emails = - roster. - stream(). + roster.parallelStream(). filter(s -> s.gender == Sex.MALE). sorted((s1, s2) -> s1.getEmailAddress().compareTo(s2.getEmailAddress())). map(Student::getName). @@ -67,13 +68,62 @@ public class Java8FunctionalTest { public void markSumAllStudentsMarksHavingEmail_Dot_de() { Assert.assertEquals( 3, //Expected marks sum 3 == 1 + 2 - roster.stream() + roster.parallelStream() .filter(p -> p.emailAddress.endsWith(".de")) // Jane and Kim .mapToInt(Student::getMark) .reduce(0, (a, b) -> a + b) ); } + /** + * A comma separated string containing all students' alphabetically ordered + * email addresses: + * + * "bob@uk.edu, fred@example.com, george@math.edu, ..." + * + */ + @Test + public void orderedCharacterSeparatedEmailList() { + + Assert.assertEquals( + "bob@uk.edu, fred@example.com, george@math.edu, jane@kiv.de, wilde@serious.de", + + roster.parallelStream(). + map(Student::getEmailAddress). + sorted(). + collect(new CsvCollector(", ")) // Using ", " as separator string. + ); + + } + + /** + * Achieves the same result as in {@link #orderedCharacterSeparatedEmailList()} + * internally using a standard collector with a modified + * {@link Collector#finisher()} method: + * + * "bob@uk.edu, fred@example.com, george@math.edu, ..." + * + */ + @Test + public void orderedCharacterSeparatedEmailList2() { + + Assert.assertEquals( + "bob@uk.edu, fred@example.com, george@math.edu, jane@kiv.de, wilde@serious.de", + + roster.parallelStream(). + map(Student::getEmailAddress). + sorted(). + collect + (Collectors.collectingAndThen + (Collectors.toList(), + l -> l.stream(). // A stream within a stream. + reduce((s1, s2) -> s1 + ", " + s2). + get() + ) + ) + ); + } + /** * Get the average mark of all female students as double value. * @@ -83,7 +133,8 @@ public class Java8FunctionalTest { @Test public void averageMarkFemaleStudents() { - final OptionalDouble femaleAverage = roster.stream(). + final OptionalDouble femaleAverage = + roster.parallelStream(). filter(s -> s.gender == Sex.FEMALE). mapToInt(Student::getMark). average(); @@ -111,10 +162,10 @@ public class Java8FunctionalTest { final Map<Student.Sex, List<String>> studentnamesBySex = roster - .stream() + .parallelStream() .collect( Collectors.groupingBy( - Student::getGender, + Student::getSex, Collectors.mapping( Student::getName, Collectors.toList()) @@ -144,7 +195,7 @@ public class Java8FunctionalTest { public void markingFrequencies() { final Map<Integer, Integer> frequencyByMark = roster - .stream() + .parallelStream() .collect( Collectors.groupingBy( Student::getMark, @@ -175,17 +226,24 @@ public class Java8FunctionalTest { * */ @Test - public void studentsByMark() { + public void studentnamessByMark() { + final Map<Integer, List<String>> namesByMark = roster - .stream() - .sorted((s1,s2) -> s1.name.compareTo(s2.name)) + .parallelStream() .collect( Collectors.groupingBy( Student::getMark, Collectors.mapping( Student::getName, - Collectors.toList()) + Collectors.collectingAndThen( + Collectors.toList(), + l -> { + Collections.sort(l); + return l; + } + ) + ) ) ); diff --git a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java index f1b9293d96dfb291859227cd3fb56098b95f67f8..4bee369422101b729a77003ffb26d10e192e2097 100644 --- a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java +++ b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/RosterTest.java @@ -14,7 +14,7 @@ import java.util.function.Predicate; */ public class RosterTest { - static final List<Student> roster = Student.createRoster(); + static final List<Student> rosterInstance = Student.createRoster(); interface CheckStudent { boolean test(Student p); @@ -23,16 +23,25 @@ public class RosterTest { // Approach 1: Create Methods that Search for Persons that Match One // Characteristic + /** + * @param roster + * @param mark + */ public static void printPersonsOlderThan(List<Student> roster, int mark) { for (Student p : roster) { if (p.getMark()>= mark) { - p.print();; + p.print(); } } } // Approach 2: Create More Generalized Search Methods + /** + * @param roster + * @param low + * @param high + */ public static void printPersonsWithinMarkRange( List<Student> roster, int low, int high) { for (Student p : roster) { @@ -46,6 +55,10 @@ public class RosterTest { // Approach 4: Specify Search Criteria Code in an Anonymous Class // Approach 5: Specify Search Criteria Code with a Lambda Expression + /** + * @param roster + * @param tester + */ public static void printPersons( List<Student> roster, CheckStudent tester) { for (Student p : roster) { @@ -57,6 +70,10 @@ public class RosterTest { // Approach 6: Use Standard Functional Interfaces with Lambda Expressions + /** + * @param roster + * @param tester + */ public static void printPersonsWithPredicate( List<Student> roster, Predicate<Student> tester) { for (Student p : roster) { @@ -68,6 +85,11 @@ public class RosterTest { // Approach 7: Use Lambda Expressions Throughout Your Application + /** + * @param roster + * @param tester + * @param block + */ public static void processPersons( List<Student> roster, Predicate<Student> tester, @@ -81,6 +103,12 @@ public class RosterTest { // Approach 7, second example + /** + * @param roster + * @param tester + * @param mapper + * @param block + */ public static void processPersonsWithFunction( List<Student> roster, Predicate<Student> tester, @@ -96,6 +124,12 @@ public class RosterTest { // Approach 8: Use Generics More Extensively + /** + * @param source + * @param tester + * @param mapper + * @param block + */ public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, @@ -109,9 +143,12 @@ public class RosterTest { } } + /** + * @param args unused + */ public static void main(String... args) { - for (Student p : roster) { + for (Student p : rosterInstance) { p.print(); } @@ -119,13 +156,13 @@ public class RosterTest { // Characteristic System.out.println("Persons older than 20:"); - printPersonsOlderThan(roster, 20); + printPersonsOlderThan(rosterInstance, 20); System.out.println(); // Approach 2: Create More Generalized Search Methods System.out.println("Persons between the ages of 14 and 30:"); - printPersonsWithinMarkRange(roster, 14, 30); + printPersonsWithinMarkRange(rosterInstance, 14, 30); System.out.println(); // Approach 3: Specify Search Criteria Code in a Local Class @@ -133,15 +170,16 @@ public class RosterTest { System.out.println("Persons who are eligible for Selective Service:"); class CheckStudentEligibleForSelectiveService implements CheckStudent { - public boolean test(Student p) { - return p.getGender() == Student.Sex.MALE + @Override + public boolean test(Student p) { + return p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3; } } printPersons( - roster, new CheckStudentEligibleForSelectiveService()); + rosterInstance, new CheckStudentEligibleForSelectiveService()); System.out.println(); @@ -152,10 +190,11 @@ public class RosterTest { "(anonymous class):"); printPersons( - roster, + rosterInstance, new CheckStudent() { + @Override public boolean test(Student p) { - return p.getGender() == Student.Sex.MALE + return p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3; } @@ -170,8 +209,8 @@ public class RosterTest { "(lambda expression):"); printPersons( - roster, - (Student p) -> p.getGender() == Student.Sex.MALE + rosterInstance, + (Student p) -> p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3 ); @@ -185,8 +224,8 @@ public class RosterTest { "(with Predicate parameter):"); printPersonsWithPredicate( - roster, - p -> p.getGender() == Student.Sex.MALE + rosterInstance, + p -> p.getSex() == Student.Sex.MALE && p.getMark() >= 18 && p.getMark() <= 25 ); @@ -199,8 +238,8 @@ public class RosterTest { "(with Predicate and Consumer parameters):"); processPersons( - roster, - p -> p.getGender() == Student.Sex.MALE + rosterInstance, + p -> p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3, p -> p.print() @@ -214,8 +253,8 @@ public class RosterTest { "(with Predicate, Function, and Consumer parameters):"); processPersonsWithFunction( - roster, - p -> p.getGender() == Student.Sex.MALE + rosterInstance, + p -> p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3, p -> p.getEmailAddress(), @@ -230,8 +269,8 @@ public class RosterTest { "(generic version):"); processElements( - roster, - p -> p.getGender() == Student.Sex.MALE + rosterInstance, + p -> p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3, p -> p.getEmailAddress(), @@ -246,10 +285,10 @@ public class RosterTest { System.out.println("Persons who are eligible for Selective Service " + "(with bulk data operations):"); - roster + rosterInstance .stream() .filter( - p -> p.getGender() == Student.Sex.MALE + p -> p.getSex() == Student.Sex.MALE && p.getMark() >= 2 && p.getMark() <= 3) .map(p -> p.getEmailAddress()) diff --git a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java index 8e3a64d9f0b73c41df643479a1fa84a66591fb8c..80c48c353c937138aa1c00cedacd1d1fc21c0537 100644 --- a/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java +++ b/P/Sda1/Streams/Template/src/main/java/de/hdm_stuttgart/mi/javastreams/Student.java @@ -34,8 +34,10 @@ package de.hdm_stuttgart.mi.javastreams; import java.util.Arrays; import java.util.List; +/** Representing students among with examination marks. */ public class Student { + /** @return Test sample of students. */ public static List<Student> createRoster() { // Create test data records final Student[] roster = new Student[] { @@ -49,52 +51,72 @@ public class Student { return Arrays.asList(roster); } + /** Male or female */ public enum Sex { - MALE("male"), FEMALE("female"); + /** */ + MALE("male"), + /** */ + FEMALE("female"); final String extern; private Sex(final String extern) {this.extern = extern;} + @Override public String toString(){return extern;} } String name; int mark; - Sex gender; + Sex sex; String emailAddress; Student(String name, int mark , - Sex gender, String email) { + Sex sex, String email) { this.name = name; this.mark = mark; - this.gender = gender; + this.sex = sex; this.emailAddress = email; } + /** * @return An examination's result */ public int getMark() { return mark; } - public Sex getGender() { - return gender; + + /** @return A student's sex. */ + public Sex getSex() { + return sex; } + /** @return A student's name. */ public String getName() { return name; } + /** @return A student's e-mail. */ public String getEmailAddress() { return emailAddress; } + /** + * Compare two students by their marks + * @param a first student + * @param b second student + * @return positive if student b's mark is better than student a's, + * zero if marks are equal, negative if ... + */ public static int compareByMark(Student a, Student b) { return a.mark - b.mark; } @Override public String toString() { - return getName() + "(" + gender + ", " + getEmailAddress() + ", mark=" + getMark() + ")"; + return getName() + "(" + sex + ", " + getEmailAddress() + ", mark=" + getMark() + ")"; } + /** + * Printing all data to standard output. + */ public void print() { System.out.println(this); } diff --git a/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java b/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java index f821150815def228d616eaabbfe37bfd3d078c86..8edfdd587690d66d7b96cd21edc18c3559fd040a 100644 --- a/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java +++ b/P/Sda1/Streams/Template/src/test/java/de/hdm_stuttgart/mi/javastreams/Java8FunctionalTest.java @@ -4,6 +4,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collector; import java.util.stream.Collectors; import org.hamcrest.Matchers; @@ -33,7 +35,6 @@ public class Java8FunctionalTest { * "Kim", 2, Student.Sex.FEMALE, "wilde@serious.de" * * ==> {"Bob", "Fred", "George"} - * */ @Test public void allMaleDistinctNameOrderedByEmail() { @@ -41,9 +42,9 @@ public class Java8FunctionalTest { final List<String> emails = roster. stream(). - filter(s -> s.gender == Sex.MALE). - sorted((s1, s2) -> s1.getEmailAddress().compareTo(s2.getEmailAddress())). + filter(s -> s.sex == Sex.MALE). map(Student::getName). + sorted(). distinct(). collect(Collectors.toList()); @@ -66,20 +67,50 @@ public class Java8FunctionalTest { * Implementation hint: Map objects to int and provide a suitable reduce operation. * You may want to read * https://docs.oracle.com/javase/tutorial/collections/streams/reduction.html#reduce - * */ @Test public void markSumAllStudentsMarksHavingEmail_Dot_de() { Assert.fail("Implement me!");// TODO } + /** + * A comma separated string containing all students' alphabetically ordered + * email addresses: + * + * "bob@uk.edu, fred@example.com, george@math.edu, ..." + * + * Hint: Read + * http://www.nurkiewicz.com/2014/07/introduction-to-writing-custom.html + * and learn how to write custom collectors. Then implement a string collector + * which uses a {@link StringBuffer} collecting the string elements. Its + * finisher() method may then return the desired result string. + */ + @Test + public void orderedCharacterSeparatedEmailList() { + Assert.fail("Implement me!");// TODO + } + + /** + * Achieves the same result as in {@link #orderedCharacterSeparatedEmailList()} + * internally using a standard collector with a modified + * {@link Collector#finisher()} method: + * + * "bob@uk.edu, fred@example.com, george@math.edu, ..." + * + * Hint: Consider {@link Collectors#collectingAndThen(Collector, Function)} and + * read http://www.adam-bien.com/roller/abien/entry/java_8_reducing_a_list. + */ + @Test + public void orderedCharacterSeparatedEmailList2() { + Assert.fail("Implement me!");// TODO + } + /** * Get the average mark of all female students as double value. * * Implementation hint: Map objects to int and provide a suitable reduce operation. * You may want to read * https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html#average-- - * */ @Test public void averageMarkFemaleStudents() { @@ -101,7 +132,6 @@ public class Java8FunctionalTest { * * Implementation hint: * stackoverflow.com/questions/2509293/map-equality-using-hamcrest#answer-29680356 - * */ @Test public void studentNamesBySex() { @@ -141,7 +171,6 @@ public class Java8FunctionalTest { * Implementation hint: Sort the stream by student's names beforehand. Then * apply a suitable * {@link Collectors#mapping(java.util.function.Function, java.util.stream.Collector)} - * */ @Test public void studentsByMark() {