package streams;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * - Collector
 * Introduced in Java 8 as means to push stream into collected values (i.e. collection)
 *
 * Collector  - interface Collector<T, A, R>
 * Collectors - final class Collectors (helper class)
 *
 */
public class AboutCollectors {
    public static void main(String[] args) {
        // collect method of Stream -- terminal operation
        // <R,A> R collect(Collector<? super T, A,R> collector)

        List<String> letters = Arrays.asList("d", "a", "y", "y", "s");

        TreeSet<String> sortedSet = letters.stream().collect(Collectors.toCollection(TreeSet::new)); // unique, ordered
        System.out.println(sortedSet); // [a, d, s, y]

        Set<String> set = letters.stream().collect(Collectors.toSet()); // unique, but order is not guaranteed
        System.out.println(set); // [a, s, d, y]

        // Collectors.joining returns Collector
        String joined = letters.stream().collect(Collectors.joining("-"));
        System.out.println(joined); // d-a-y-y-s

        // Stream to collectedly average value
        List<String> message = Arrays.asList("Save", "the", "earth");
        Double averagedValue = message.stream().collect(Collectors.averagingInt(String::length));
        System.out.println(averagedValue); // 4.0

        // Stream to map
        // toMap take KeyMapper, ValueMapper (MergeFunction and mapSupplier)
        List<String> words = Arrays.asList("Save", "this", "earth");
        Map<String, Integer> mappedWords1 = words.stream().collect(Collectors.toMap(k -> k.toUpperCase(), String::length));
        System.out.println(mappedWords1);   // {EARTH=5, THIS=4, SAVE=4}

        // Throws RuntimeException: Duplicate key SAVE
//        Map<Integer, String> mappedWords2 = words.stream().collect(Collectors.toMap(String::length, k -> k.toUpperCase()));

        // Using mergeFunction to resolve duplicate key issue
        Map<Integer, String> mappedWords3 = words.stream().collect(Collectors.toMap(String::length, k -> k, (k1, k2) -> k1 + ":" + k2));
        System.out.println(mappedWords3);   // {4=Save:this, 5=earth}

        // Using mergeFunction and mapSupplier
        TreeMap<Integer, String> mappedWords4 = words.stream().collect(Collectors.toMap(String::length, k -> k, (k1, k2) -> k1 + ":" + k2, TreeMap::new));
        System.out.println(mappedWords4);   // {4=Save:this, 5=earth}

        // Group, Partition and Map
        // Collectors.groupingBy takes Classifier and ()
        Map<Integer, List<String>> map1 = words.stream().collect(Collectors.groupingBy(String::length));
        System.out.println(map1);  // {4=[Save, this], 5=[earth]}

        Map<Integer, Set<String>> map2 = words.stream()
                .collect(Collectors.groupingBy(String::length, Collectors.toSet()));
        System.out.println(map2);  // {4=[this, Save], 5=[earth]}

        TreeMap<Integer, Set<String>> map3 = words.stream()
                .collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toSet()));
        System.out.println(map3);  // {4=[this, Save], 5=[earth]}

        // Collectors.partitioningBy take Predicate as parameter
        Map<Boolean, List<String>> map4 = words.stream().map(x -> x.toString())
                .collect(Collectors.partitioningBy(x -> x.length() > 4));
        System.out.println(map4);  // {false=[Save, this], true=[earth]}

        // partitioningBy returns map with always/only 2 keys, (true & false)
        // Below example shows difference between partitioningBy vs groupingBy in case of empty stream
        Map<Boolean, List<String>> map5 = Stream.empty().map(x -> (String)x)
                .collect(Collectors.partitioningBy(x -> x.startsWith("x")));
        System.out.println(map5); // {false=[], true=[]}

        Map<Boolean, List<String>> map6 = Stream.empty().map(x -> (String)x)
                .collect(Collectors.groupingBy(x -> x.startsWith("x")));
        System.out.println(map6); // {}

        // mapping, minBy, maxBy
//        Map<Integer, Optional<Character>> map6 = words.stream().map(x -> (String)x).collect(
//                Collectors.groupingBy(
//                        String::length,
//                        Collectors.mapping(s -> s.charAt(0),
//                                Collectors.minBy(Comparator.naturalOrder()))));
//        System.out.println(map6); // {5=Optional[b], 6=Optional[t]}
        //System.out.println(words.stream());

    }
}

@@ -240,16 +240,32 @@
 llection
+ or single value
 )%0A *%0A * 
@@ -443,16 +443,88 @@
 args) %7B%0A
+        List%3CString%3E letters = Arrays.asList(%22d%22, %22a%22, %22y%22, %22y%22, %22s%22);%0A%0A
         
@@ -587,16 +587,17 @@
   // %3CR,
+ 
 A%3E R col
@@ -642,81 +642,8 @@
 or)%0A
-%0A        List%3CString%3E letters = Arrays.asList(%22d%22, %22a%22, %22y%22, %22y%22, %22s%22);%0A%0A
     
@@ -792,24 +792,25 @@
 (sortedSet);
+ 
  // %5Ba, d, s
@@ -958,16 +958,23 @@
 ln(set);
+       
  // %5Ba, 
@@ -1141,16 +1141,20 @@
 joined);
+    
  // d-a-
@@ -2089,32 +2089,49 @@
 = words.stream()
+%0A                
 .collect(Collect
@@ -2365,32 +2365,49 @@
 = words.stream()
+%0A                
 .collect(Collect
@@ -2695,32 +2695,49 @@
 = words.stream()
+%0A                
 .collect(Collect

Update notes/examples about Collector