diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/PrimeNumbersCollector.java b/modern-java/src/main/java/org/gym/fp/moderjava/PrimeNumbersCollector.java new file mode 100644 index 0000000..7c37896 --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/PrimeNumbersCollector.java @@ -0,0 +1,56 @@ +package org.gym.fp.moderjava; + +import java.util.*; +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; + +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; + +public class PrimeNumbersCollector implements Collector>, Map>> { + + @Override + public Supplier>> supplier() { + return () -> new HashMap<>() {{ + put(true, new ArrayList<>()); + put(false, new ArrayList<>()); + }}; + } + + @Override + public BiConsumer>, Integer> accumulator() { + return (Map> acc, Integer candidate) -> { + acc.get(isPrime(acc.get(true), candidate)) + .add(candidate); + }; + } + + @Override + public BinaryOperator>> combiner() { + return (Map> map1, Map> map2) -> { + map1.get(true).addAll(map2.get(true)); + map1.get(false).addAll(map2.get(false)); + return map1; + }; + } + + @Override + public Function>, Map>> finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH)); + } + + private boolean isPrime(List primes, int candidate) { + int candidateRoot = (int) Math.sqrt(candidate); + return primes.stream() + .takeWhile(i -> i <= candidateRoot) + .noneMatch(i -> candidate % i == 0); + } + +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java b/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java index 5046252..aec6962 100644 --- a/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java +++ b/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java @@ -128,7 +128,7 @@ public class StreamTest { new Transaction(mario, 2012, 710), new Transaction(mario, 2012, 700), new Transaction(alan, 2012, 950) - ); + ); out.println("1)"); transactions.stream() .filter(t -> t.getYear() == 2011) @@ -183,7 +183,7 @@ public class StreamTest { .flatMap(a -> IntStream.rangeClosed(a, 100) .mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)}) .filter(t -> t[2] % 1 == 0) - ) + ) .limit(5) .forEach(triple -> out.printf("(%.0f, %.0f, %.0f)\n", triple[0], triple[1], triple[2])); out.println("----------------------------------------"); @@ -231,14 +231,14 @@ public class StreamTest { Map> caloricDishesByType = menu.stream() .collect(groupingBy(Dish::getType, - filtering(dish -> dish.getCalories() > 500, - toList()))); + filtering(dish -> dish.getCalories() > 500, + toList()))); out.println(caloricDishesByType); out.println("----------------------------------------"); Map> dishNamesByType = menu.stream() .collect(groupingBy(Dish::getType, - mapping(Dish::getName, toList()))); + mapping(Dish::getName, toList()))); out.println(dishNamesByType); out.println("----------------------------------------"); Map> dishTags = Map.of( @@ -251,18 +251,18 @@ public class StreamTest { "pizza", asList("tasty", "salty"), "prawns", asList("tasty", "roasted"), "salmon", asList("delicious", "fresh") - ); + ); Map> dishNamesByType2 = menu.stream() .collect(groupingBy(Dish::getType, - flatMapping(dish -> { - // FIXME: The example of book is not safety - List tags = dishTags.get(dish.getName()); - if (tags != null) { - return tags.stream(); - } - return Stream.empty(); - }, toSet()))); + flatMapping(dish -> { + // FIXME: The example of book is not safety + List tags = dishTags.get(dish.getName()); + if (tags != null) { + return tags.stream(); + } + return Stream.empty(); + }, toSet()))); out.println(dishNamesByType2); out.println("----------------------------------------"); Map>> dishesByTypeCaloricLevel = @@ -278,6 +278,43 @@ public class StreamTest { }))); out.println(dishesByTypeCaloricLevel); out.println("----------------------------------------"); + Map> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian)); + out.println(partitionedMenu); + out.println("----------------------------------------"); + Map>> vegetarianDishesByType = + menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType))); + out.println(vegetarianDishesByType); + out.println("----------------------------------------"); + Map mostCaloricPartitionedByVegetarian = + menu.stream() + .collect(partitioningBy(Dish::isVegetarian, + collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get))); + out.println(mostCaloricPartitionedByVegetarian); + out.println("----------------------------------------"); + out.println(partitionPrimes(100)); + out.println("----------------------------------------"); + List dishes = menu.stream() + .filter(Dish::isVegetarian) + .collect(new ToListCollector<>()); + out.println(dishes); + out.println("----------------------------------------"); + out.println(partitionPrimesWithCustomCollector(100)); + out.println("----------------------------------------"); + } + + public static Map> partitionPrimes(int n) { + return IntStream.rangeClosed(2, n).boxed() + .collect(partitioningBy(candidate -> isPrime(candidate))); + } + + public static boolean isPrime(int candidate) { + return IntStream.rangeClosed(2, candidate - 1) + .limit((long) Math.floor(Math.sqrt(candidate)) - 1) + .noneMatch(i -> candidate % i == 0); + } + + static Map> partitionPrimesWithCustomCollector(int n) { + return IntStream.rangeClosed(2, n).boxed().collect(new PrimeNumbersCollector()); } enum CaloricLevel {DIET, NORMAL, FAT} @@ -291,7 +328,7 @@ public class StreamTest { new Dish("tomato", 30, Dish.Type.VEGETARIAN), new Dish("tunny", 120, Dish.Type.FISH), new Dish("potato", 70, Dish.Type.VEGETARIAN) - ); + ); return Collections.unmodifiableList(dishes); } diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/ToListCollector.java b/modern-java/src/main/java/org/gym/fp/moderjava/ToListCollector.java new file mode 100644 index 0000000..f163a3e --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/ToListCollector.java @@ -0,0 +1,68 @@ +package org.gym.fp.moderjava; + +import java.util.*; +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; + +import static java.util.stream.Collector.Characteristics.CONCURRENT; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; + +// Collector => +// T = tipo generico degli elementi nello stream +// A = tipo dell'accumulatore, risultato parziale della riduzione +// R = tipo dell'oggetto risultato dall'operazione +public class ToListCollector implements Collector, List> { + + /** + * @return l'accumulatore vuoto + */ + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + /** + * @return la funzione che accumula gli elementi T in A + */ + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + /** + * Questo metodo รจ invocato per combinare gli accumulatori + * elaborati da diversi sotto-stream + * + * @return la combinazione degli accumulatori + */ + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } + + /** + * Questo metodo viene invocato al termine del processo + * di accumulazione degli elementi dello {@link java.util.stream.Stream} + * + * @return la funzione che trasforma A in R + */ + @Override + public Function, List> finisher() { + return Function.identity(); + } + + /** + * @return insieme dei comportamenti che il collector deve assumere + */ + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT)); + } + +}