package lambda;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Reduce {

	public static void main(String[] args) {


		/**
		 * Exemple 5
		 */
		//reduce is fold (also termed reduce, accumulate, aggregate, compress, or inject) refers to 
		//a family of higher-order functions that analyze a recursive data structure and through use 
		//of a given combining operation, recombine the results of recursively processing its constituent 
		//parts, building up a return value. 
		//via wiki

		//Actually, the methods as sum(), max(), min(), they are implementations by using reduce
		
		System.out.println("Exemple5");
		
		System.out.println(IntStream.rangeClosed(1, 10).sum());
		
		System.out.println(IntStream.rangeClosed(1, 10).count());
		
		System.out.println(IntStream.rangeClosed(1, 10).max().orElse(-1));
		
		System.out.println(IntStream.rangeClosed(1, 10).min().orElse(-1));
		
		
		//reduce has two overloads with IntStream, one is with only one parameter, anther one is with two,
		//reduce(operation) or reduce(initValue,operation)
		//the only different is the second one has an initial value, some time we have to have one
		//
		//there is 3rd overload, I'll show you later.
		

		//sum by reduce, if you remove 0, the default value, it will return an optional<Integer>
		System.out.println(IntStream.rangeClosed(1, 10).reduce(0, (x,y)-> x+y));
		//count by reduce
		System.out.println(IntStream.rangeClosed(1, 10).reduce(0, (x,y)-> {return y = ++x;}));
		//max by reduce, I set mini value as the default value 
		System.out.println(IntStream.rangeClosed(1, 10).reduce(Integer.MIN_VALUE, (x,y)-> {return x>y ? x:y;}));
		//min by reduce, I didn't set the default value, 
		System.out.println(IntStream.rangeClosed(1, 10).reduce( (x,y)-> {return x<y ? x:y;}).orElse(-1));
		
		
		
		
		//this is the reduce with 3 parameters,
		//this one is a little bit difficult, the other reduces can only return a value which has 
		//the same value as the input, but this one can return what you want
		//just make sure the second parameter and the 3rd have the same type as output
		//this exemple is the same functionality as collect(Collectors.toList()),
		//it just shows you how the reduce works,
		//the initial value is an ArrayList, an empty list of course as the first couple:(list, Integer)
		//second parameter is a function which will put the integer into the list, and return the list
		//for the next couple,
		//3rd parameter, is useless for us, it will work only we use .parallel() as stream().
		//so it's why I return an null
		//this exemple can be used with paralel(), because of ArrayList, it's not a thread safe collection
		System.out.println(IntStream.rangeClosed(1, 10).boxed()
			.reduce(new ArrayList<Integer>()
					, (List<Integer> x, Integer y) -> 
									{x.add(y); return x;}
					, (x, y) -> {return null;}));
		
		//the same as blow
		System.out.println(IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toCollection(ArrayList::new)));
		//if you don't care about of which list implementation,
		System.out.println(IntStream.rangeClosed(1, 10).boxed().collect(Collectors.toList()));
		
		

		
		/*
		 * Exemple 6
		 */
		
		//take a guess, for this one?
		//parallel().reduce(2,(x,y)->x*y,(x,y)->x+y)
		//2 * 2  =  4
		//2 * 4  =  8
		//2 * 6  =  12
		//2 * 8  =  16
		//2 * 10 =  20
		//4+8+12+16+20 = 60
		//because the elements are sent as parallel,
		System.out.println(
				Stream.of(2,4,6,8,10)
				.parallel()
				.reduce(2,(x,y)->x*y,(x,y)-> x+y)
				);
		
		//take a guess, for this one?
		//reduce(2,(x,y)->x*y,(x,y)->x+y)(without parallel())
		// 2 * 2 * 4 * 6 * 8 * 10 = 7680
		// the first 2 is the first parameter in the reduce method
		//the element are not sent as parallel, so they are all straight outta 
		
		System.out.println(
				Stream.of(2,4,6,8,10)
				//.parallel()
				.reduce(2,(x,y)->x*y,(x,y)-> x+y)
				);
		
		//so they will be the same answer
		System.out.println(
				Stream.of(2,4,6,8,10)
				//.parallel()
				.reduce(2,(x,y)->x*y)
				);
		
		//so back to the arrayList one, as the arrayList is not thread safe, 
		//we can't use parallel(), why we can't use the reduce which has two parameters?
		//the reduce which has two parameters will only return the same type as input
		//so you can't do something like this, 
		//Stream.of(2,4,6,8,10)
		//.reduce(new ArrayList<Integer>(),
		//		(List<Integer> x, Integer y) -> {x.add(y); return x;});
		// the type of x is List, it has to be Integer.
		System.out.println(
				Stream.of(2,4,6,8,10)
				.reduce(new ArrayList<Integer>(),
						(List<Integer> x, Integer y) -> {x.add(y); return x;},
						(x,y) -> null)
				);
		
		
	}

}
