Friday 31 October 2014

Java 8 - Collecting Data With Streams


Here's some examples of various ways we can collect Data with streams

GIVEN : A Menu which contains a list of Dishes
        List menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));

Dish(String name, boolean vegetarian, int calories, Type type)

Count the number of Dishes:
 
menu.stream().count()
menu.stream().collect(Collectors.counting())

Finding maximum and minimum in a Stream of values
 
 Optional highCalorieDish = menu.stream()
                .collect(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)));
Summarization - the below methods are avaialble in Collectors for Summarization Collectors.summingInt Collectors.summingLong Collectors.summingDouble
 We also have equivalent for average Collectors.averagingInt/averagingLong/averagingDouble
 
int totalCalories =  menu.stream().collect(Collectors.summingInt(Dish::getCalories));
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));

This Collector gathers all that information in a class called IntSummaryStatistics
 
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

IntSummaryStatistics{count=9, sum=4300, min=120,average=477.777778, max=800}

Joining Strings -Collector which reduces a Stream to a single result doesn’t operate on
numeric values but allows a concatenation of Strings
 
String shortMenu = menu.stream().map(Dish::getName).collect(joining(", "));

Generalized summarization with reduction - Collectors.reducing factory method is a generalization of all of the above specialized cases.
 
int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, (Integer i, Integer j) -> i + j));

Optional mostCalorificDish =menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

Various ways to find the total number of calories in the menu:
Using Data Processing way :
System.out.println("Using Data Processing way ...with reduce " + menu.stream().map(Dish::getCalories).reduce(0, Integer::sum));
System.out.println("Using Data Processing way ..."               + menu.stream().mapToInt(Dish::getCalories).sum());

Using Data Collecting way
 System.out.println("Using Data Collecting way ..."               + menu.stream().collect(summingInt(Dish::getCalories)));
 System.out.println("Using Data Collecting way with reduction..." + menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum)));

Grouping - Group the dishes based on the type using Collectors.groupingBy factory method

Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));

{
FISH=[prawns, salmon], 
OTHER=[french fries, rice, season fruit, pizza],
MEAT=[pork, beef, chicken]
}
Here, you pass to the groupingBy method a Function extracting the corresponding Dish.Type for each Dish in the Stream. We call this Function a classification function because it’s used to classify the elements of the Stream in different groups.

Complex Classification Ex -
classify as “diet” all dishes with 400 calories or fewer,
set to “normal” the dishes having between 400 and 700 calories, and
set to “fat” the ones with more than 700 calories.

 public enum CaloricLevel {DIET, NORMAL, FAT}
  Map<CaloricLevel, List<Dish>> dishMap = menu.stream()
                .collect(groupingBy(
                        dish -> {
                            if (dish.getCalories() <= 400) return Dish.CaloricLevel.DIET;
                            else if (dish.getCalories() <= 700) return Dish.CaloricLevel.NORMAL;
                            else return Dish.CaloricLevel.FAT;
                        }
                ));

Collecting data in subgroups
1. count the number of dishes for each type
 
Map<Dish.Type, Long> numberOfDishesByType = menu.stream()
                .collect(groupingBy(
                        Dish::getType,
                        Collectors.counting()
                ));


2. retrieves total calories of each Dish type
 
Map<Dish.Type, Integer> integerMap = menu.stream()
                .collect(
                        Collectors.groupingBy(
                                Dish::getType,
                                Collectors.reducing(0, Dish::getCalories, (a, b) -> a + b)
                        )
                );

Map<Dish.Type, Integer> totalCaloriesByType = menu.stream()
                .collect(
                        groupingBy(
                             Dish::getType,
                             summingInt(Dish::getCalories)
                             )
                     );

3. Calories available in the menu for each type of dish
 
Map<Dish.Type, List<Integer>> typeListMap = menu.stream()
                .collect(
                        groupingBy(
                                Dish::getType,
                                mapping(
                                        Dish::getCalories,
                                        toList()
                                )
                        )
                );

{
FISH=[300, 450], 
OTHER=[530, 350, 120, 550], 
MEAT=[800, 700, 400]
}