All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codefilarete.tool.collection.Iterables Maven / Gradle / Ivy

package org.codefilarete.tool.collection;

import javax.annotation.Nullable;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.SortedMap;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.codefilarete.tool.Duo;
import org.codefilarete.tool.collection.PairIterator.UntilBothIterator;
import org.codefilarete.tool.trace.ModifiableInt;

/**
 * @author Guillaume Mary
 */
public final class Iterables {
	
	/**
	 * Transforms an {@link Iterator} to an {@link Iterable}
	 * 
	 * @param iterator the surrogate
	 * @param  the content typed of the {@link Iterator}
	 * @return an {@link Iterable} which {@link Iterable#iterator()} returns the given {@link Iterator}
	 */
	public static  Iterable asIterable(Iterator iterator) {
		return () -> iterator;
	}
	
	/**
	 * @param iterable a nullable {@link Iterable}
	 * @return the first element of the argument or null if argument is empty
	 */
	@Nullable
	public static  E first(@Nullable Iterable iterable) {
		return first(iterable, null);
	}
	
	/**
	 * @param iterable a nullable {@link Iterable}
	 * @return the first element of the argument or the given default value if argument is empty
	 */
	public static  E first(@Nullable Iterable iterable, E defaultValue) {
		if (iterable == null) {
			return defaultValue;
		} else {
			return first(iterable.iterator());
		}
	}
	
	/**
	 * @param iterator a nullable {@link Iterator}
	 * @return the first element of the argument or null if argument is empty
	 */
	@Nullable
	public static  E first(Iterator iterator) {
		return first(iterator, null);
	}
	
	/**
	 * @param iterator a nullable {@link Iterator}
	 * @return the first element of the argument or the given default value if argument is empty
	 */
	public static  E first(Iterator iterator, E defaultValue) {
		return iterator != null && iterator.hasNext() ? iterator.next() : defaultValue;
	}
	
	/**
	 * Optimized version of {@link #first(Iterable)} for {@link List} : will use {@code get(0)} on the list to get first element (instead of hasNext())
	 *
	 * @param iterable a nullable {@link List}
	 * @return the first element of the argument or null if argument is empty
	 */
	@Nullable
	public static  E first(List iterable) {
		return first(iterable, null);
	}
	
	/**
	 * Optimized version of {@link #first(Iterable)} for {@link List} : will use {@code get(0)} on the list to get first element (instead of hasNext())
	 * 
	 * @param iterable a nullable {@link List}
	 * @return the first element of the argument or the given default value if argument is empty
	 */
	public static  E first(List iterable, E defaultValue) {
		if (Collections.isEmpty(iterable)) {
			return defaultValue;
		} else {
			return iterable.get(0);
		}
	}
	
	/**
	 * Optimized version of {@link #first(Iterable)} for arrays : will use {@code [0]} on the array to get first element (instead of hasNext())
	 *
	 * @param array a nullable array
	 * @return the first element of the argument or null if argument is empty
	 */
	@Nullable
	public static  E first(E[] array) {
		return first(array, null);
	}
	
	/**
	 * Optimized version of {@link #first(Iterable)} for arrays : will use {@code [0]} on the array to get first element (instead of hasNext())
	 *
	 * @param array a nullable array
	 * @return the first element of the argument or the given default value if argument is empty
	 */
	public static  E first(E[] array, E defaultValue) {
		if (Arrays.isEmpty(array)) {
			return defaultValue;
		} else {
			return array[0];
		}
	}
	
	/**
	 * Returns the first entry of a {@link Map}, only makes sense on a {@link SortedMap} or a {@link LinkedHashMap}
	 * 
	 * @param iterable a Map : Sorted or Linked (else it has no purpose)
	 * @return the first {@link Entry} of the Map, or null if argument is empty or null
	 */
	@Nullable
	public static  Map.Entry first(@Nullable Map iterable) {
		return first(iterable, null);
	}
	
	/**
	 * Returns the first entry of a {@link Map}, only makes sense on a {@link SortedMap} or a {@link LinkedHashMap}
	 *
	 * @param iterable a Map : Sorted or Linked (else it has no purpose)
	 * @return the first {@link Entry} of the Map, or the given default {@link Entry} if argument is empty or null
	 */
	public static  Map.Entry first(@Nullable Map iterable, Map.Entry defaultValue) {
		if (iterable == null) {
			return defaultValue;
		} else {
			return first(iterable.entrySet());
		}
	}
	
	/**
	 * Returns the first value of a {@link Map}, only makes sense on a {@link SortedMap} or a {@link LinkedHashMap}
	 *
	 * @param iterable a Map : Sorted or Linked (else it has no purpose)
	 * @return the first value of the Map, null if argument is null
	 */
	@Nullable
	public static  V firstValue(@Nullable Map iterable) {
		return firstValue(iterable, null);
	}
	
	/**
	 * Returns the first value of a {@link Map}, only makes sense on a {@link SortedMap} or a {@link LinkedHashMap}
	 *
	 * @param iterable a Map : Sorted or Linked (else it has no purpose)
	 * @return the first value of the Map, or the given default value if argument is empty or null
	 */
	@Nullable
	public static  V firstValue(@Nullable Map iterable, V defaultValue) {
		Entry firstEntry = first(iterable);
		return firstEntry == null ? defaultValue : firstEntry.getValue();
	}
	
	/**
	 * Indicates if an {@link Iterable} is empty or not.
	 * Implementation is optimized for {@link Collection}s by using {@link Collection#isEmpty()}, else will use {@link Iterator#hasNext()}.
	 * 
	 * @param iterable a nullable {@link Iterable}
	 * @return true if given argument is null or has no element
	 */
	public static boolean isEmpty(@Nullable Iterable iterable) {
		return iterable == null
				|| ((iterable instanceof Collection) ? ((Collection) iterable).isEmpty() : !iterable.iterator().hasNext()) ;
	}
	
	/**
	 * @param iterable a nullable {@link List}
	 * @return the last element of the argument or null if argument is empty or null
	 */
	@Nullable
	public static  E last(@Nullable List iterable) {
		return last(iterable, null);
	}
	
	/**
	 * @param iterable a nullable {@link List}
	 * @return the last element of the argument or the given default value if argument is empty or null
	 */
	public static  E last(@Nullable List iterable, E defaultValue) {
		if (iterable == null || iterable.isEmpty()) {
			return defaultValue;
		} else {
			return iterable.get(iterable.size() - 1);
		}
	}
	
	/**
	 * @param iterable a nullable {@link Iterable}
	 * @return the last element of the argument or null
	 */
	public static  E last(@Nullable Iterable iterable) {
		return last(iterable, null);
	}
	
	/**
	 * @param iterable a nullable {@link Iterable}
	 * @param defaultValue value to be return if iterable is empty
	 * @return the last element of the argument or the given default value if argument is empty or null
	 */
	public static  E last(@Nullable Iterable iterable, @Nullable E defaultValue) {
		if (iterable == null) {
			return defaultValue;
		} else {
			Iterator iterator = iterable.iterator();
			E result = null;
			if (!iterator.hasNext()) {
				result = defaultValue;
			}
			while (iterator.hasNext()) {
				result = iterator.next();
			}
			return result;
		}
	}
	
	/**
	 * Collects iterable elements until a given element
	 * 
	 * @param iterable the iterable to scan
	 * @param untilExcluded stopping element
	 * @param  elements type
	 * @return firsts elements of the iterable that differ from {@code untilExcluded}, in the iteration order, without {@code untilExcluded}
	 */
	public static  List head(Iterable iterable, E untilExcluded) {
		List result = new ArrayList<>();
		for (E e : iterable) {
			if (untilExcluded.equals(e)) {
				break;
			} else {
				result.add(e);
			}
		}
		return result;
	}
	
	/**
	 * Maps an {@link Iterable} on a key took as a {@link Function} of its beans. The value is also a function took on them.
	 * 
	 * @param iterable the iterable to map
	 * @param keyMapper the key provider
	 * @param valueMapper the value provider
	 * @param  iterated objets type
	 * @param  keys type
	 * @param  values type
	 * @return a new (Hash)Map
	 */
	public static  Map map(Iterable iterable,
										  Function keyMapper,
										  Function valueMapper) {
		return map(iterable, keyMapper, valueMapper, HashMap::new);
	}
	
	/**
	 * Maps an {@link Iterable} on a key took as a {@link Function} of its beans. The value is also a function took on them.
	 *
	 * @param iterable the iterable to map
	 * @param keyMapper the key provider
	 * @param valueMapper the value provider
	 * @param target the map to be filled
	 * @param  iterated objets type
	 * @param  keys type
	 * @param  values type
	 * @param  returned {@link Map} type
	 * @return a new {@link Map} of type M filled with key and values from given parameters
	 */
	public static > M map(Iterable iterable,
													   Function keyMapper,
													   Function valueMapper,
													   Supplier target) {
		M result = target.get();
		for (T t : iterable) {
			result.put(keyMapper.apply(t), valueMapper.apply(t));
		}
		return result;
	}
	
	/**
	 * Maps objects of an {@link Iterable} over a key took as a {@link Function} of them. 
	 * 
	 * @param iterable the iterable to map
	 * @param keyMapper the key provider
	 * @param  iterated objets type
	 * @param  keys type
	 * @return a new (Hash)Map
	 */
	public static  Map map(Iterable iterable, Function keyMapper) {
		return map(iterable, keyMapper, Function.identity());
	}
	
	/**
	 * Maps an {@link Iterable} on a key took as a {@link Function} of its beans. The value is also a function took on them.
	 *
	 * @param iterable the iterable to map
	 * @param keyMapper the key provider
	 * @param target the map to be filled
	 * @param  iterated objets type
	 * @param  keys type
	 * @param  returned {@link Map} type
	 * @return a new {@link Map} of type M filled with key and values from given parameters
	 */
	public static > M map(Iterable iterable, Function keyMapper, Supplier target) {
		return map(iterable, keyMapper, Function.identity(), target);
	}
	
	/**
	 * Equivalent to {@link #collect(Iterable, Function, Supplier)} collecting to a {@link List}
	 * 
	 * @param iterable the source
	 * @param mapper the mapping function
	 * @param  input type
	 * @param  output type
	 * @return the collection given by the supplier
	 */
	public static  List collectToList(Iterable iterable, Function mapper) {
		return collect(iterable, mapper, ArrayList::new);
	}
	
	/**
	 * Applies a mapper over an {@link Iterable} and puts each object into a collection.
	 * 
	 * @param iterable the source
	 * @param mapper the mapping function
	 * @param target the supplier of resulting collection
	 * @param  the input type
	 * @param  the output type
	 * @param  the collecting type
	 * @return the collection given by the supplier
	 */
	public static > C collect(Iterable iterable, Function mapper, Supplier target) {
		return collect(iterable, i -> true, mapper, target);
	}
	
	/**
	 * Applies a filter and a mapper over an {@link Iterable} to collect objects and puts them into a collection.
	 *
	 * @param iterable the source
	 * @param acceptFilter the accepting condition
	 * @param mapper the mapping function
	 * @param target the supplier of resulting collection
	 * @param  the input type
	 * @param  the output type
	 * @param  the collecting type
	 * @return the collection given by the supplier
	 */
	public static > C collect(Iterable iterable, Predicate acceptFilter, Function mapper,
															Supplier target) {
		return collect(iterable, acceptFilter, mapper, x -> true, target);
	}
	
	/**
	 * Applies a filter and a mapper over an {@link Iterable} to collect objects and puts them into a collection if it's accepted by a second
	 * predicate.
	 *
	 * @param iterable the source
	 * @param acceptFilter the accepting condition
	 * @param mapper the mapping function
	 * @param mappedValueFilter the accepting condition for the mapped value
	 * @param target the supplier of resulting collection
	 * @param  the input type
	 * @param  the output type
	 * @param  the collecting type
	 * @return the collection given by the supplier
	 */
	public static > C collect(Iterable iterable, Predicate acceptFilter, Function mapper,
															Predicate mappedValueFilter, Supplier target) {
		C result = target.get();
		for (I pawn : iterable) {
			if (acceptFilter.test(pawn)) {
				O mappedPawn = mapper.apply(pawn);
				if (mappedValueFilter.test(mappedPawn)) {
					result.add(mappedPawn);
				}
			}
		}
		return result;
	}
	
	/**
	 * Copies an {@link Iterable} to a {@link List}
	 * 
	 * @param iterable an {@link Iterable}, not null
	 * @return a new {@link List} containing all elements of iterable
	 */
	public static  List copy(Iterable iterable) {
		return copy(iterable, new ArrayList<>());
	}
	
	/**
	 * Copies an {@link Iterator} to a {@link List}
	 * 
	 * @param iterator an {@link Iterator}, not null
	 * @return a new {@link List} containing all elements of iterator
	 */
	public static  List copy(Iterator iterator) {
		return copy(iterator, new ArrayList<>());
	}
	
	/**
	 * Copies an {@link Iterable} to a given {@link Collection}
	 *
	 * @param iterable an {@link Iterable}, not null
	 * @return the given {@link List}
	 */
	public static > C copy(Iterable iterable, C result) {
		if (iterable instanceof Collection) {
			result.addAll((Collection) iterable);
		} else {
			copy(iterable.iterator(), result);
		}
		return result;
	}
	
	/**
	 * Copies an {@link Iterator} to a given {@link List}
	 *
	 * @param iterator an {@link Iterator}, not null
	 * @return the given {@link List}
	 */
	public static > C copy(Iterator iterator, C result) {
		iterator.forEachRemaining(result::add);
		return result;
	}
	
	/**
	 * Gives the intersection between two {@link Collection}s.
	 * Implementation has no particular optimization, it is based on a {@link HashSet}, hence comparison is based on
	 * {@link Object#equals(Object)}).
	 * 
	 * @param c1 a {@link Collection}, not null
	 * @param c2 a {@link Collection}, not null
	 * @param  type of elements
	 * @return the intersection between two {@link Collection}s
	 */
	public static  Set intersect(Collection c1, Collection c2) {
		Set copy = new HashSet<>(c1);
		copy.retainAll(c2);
		return copy;
	}
	
	/**
	 * Gives the intersection between two {@link Collection}s by comparing objects with a {@link Comparator}
	 * Implementation has no particular optimization, it is based on a {@link java.util.TreeSet}.
	 *
	 * @param c1 a {@link Collection}, not null
	 * @param c2 a {@link Collection}, not null
	 * @param  type of elements
	 * @return the intersection between two {@link Collection}s according to the given {@link Comparator}
	 */
	public static  Set intersect(Collection c1, Collection c2, BiPredicate predicate) {
		Set copy = new HashSet<>(c1);
		copy.removeIf(e -> !contains(c2, c -> predicate.test(e, c)));
		return copy;
	}
	
	/**
	 * Gives the complement of c2 in c1 : all elements of c1 that are not member of c2
	 * Implementation has no particular optimization, it is based on a {@link HashSet#removeAll(Collection)} and as such
	 * is based on {@link Object#equals}
	 * 
	 * @param c1 a {@link Collection}, not null
	 * @param c2 a {@link Collection}, not null
	 * @param  type of elements
	 * @return the complement of c1 in c2
	 */
	public static  Set minus(Collection c1, Collection c2) {
		return minus(c1, c2, (Function, HashSet>) HashSet::new);
	}
	
	/**
	 * Gives the complement of c2 in c1 : all elements of c1 that are not member of c2
	 * Implementation has no particular optimization, it is based on a {@link HashSet#removeAll(Collection)} and as such
	 * is based on {@link Object#equals}.
	 * Result is pushed to given resultHolder.
	 *
	 * @param c1 a {@link Collection}, not null
	 * @param c2 a {@link Collection}, not null
	 * @param resultHolder function expected to give resulting {@link Set}. Argument is c1.
	 * @param  type of elements
	 * @return the complement of c1 in c2
	 */
	public static > S minus(Collection c1, Collection c2, Function, S> resultHolder) {
		S copy = resultHolder.apply(c1);
		copy.removeAll(c2);
		return copy;
	}
	
	/**
	 * Gives the complement of c2 in c1 : all elements of c1 that are not member of c2 by comparing objects with a {@link Comparator}
	 * Implementation has no particular optimization, it is based on a {@link java.util.TreeSet}.
	 *
	 * @param c1 a {@link Collection}, not null
	 * @param c2 a {@link Collection}, not null
	 * @param  type of elements
	 * @return the complement of c1 in c2
	 */
	public static  Set minus(Collection c1, Collection c2, BiPredicate predicate) {
		Set copy = new HashSet<>(c1);
		copy.removeIf(e -> contains(c2, c -> predicate.test(e, c)));
		return copy;
	}
	
	public static  boolean equals(Iterable it1, Iterable it2, BiPredicate predicate) {
		if (it1 == it2)
			return true;
		
		Iterator e1 = it1.iterator();
		Iterator e2 = it2.iterator();
		while (e1.hasNext() && e2.hasNext()) {
			E o1 = e1.next();
			E o2 = e2.next();
			if ((o1 == null ^ o2 == null) || !predicate.test(o1, o2)) {
				return false;
			}
		}
		return !(e1.hasNext() || e2.hasNext());
	}
	
	
	/**
	 * Puts 2 {@link Iterator}s side by side in a {@link Map}.
	 * If {@link Iterator}s are not of same length then null elements will be used into the {@link Map}.
	 * This leads to a cumulative null key if {@code values} {@link Iterator} is larger than {@code keys} {@link Iterator} because all overflowing
	 * elements will be put onto a null key.
	 * 
	 * @param keys {@link Iterator} which elements will be used as keys
	 * @param values {@link Iterator} which elements will be used as values
	 * @param  type of keys
	 * @param  type of values
	 * @return a new {@link HashMap} composed of keys and values from both {@link Iterator}s
	 */
	public static  Map pair(Iterable keys, Iterable values) {
		return pair(keys, values, HashMap::new);
	}
	
	/**
	 * Puts 2 {@link Iterator}s side by side in a {@link Map}.
	 * If {@link Iterator}s are not of same length then null elements will be used into the {@link Map}.
	 * This leads to a cumulative null key if {@code values} {@link Iterator} is larger than {@code keys} {@link Iterator} because all overflowing
	 * elements will be put onto a null key.
	 *
	 * @param keys {@link Iterator} which elements will be used as keys
	 * @param values {@link Iterator} which elements will be used as values
	 * @param target a provider of a {@link Map}
	 * @param  type of keys
	 * @param  type of values
	 * @return a new {@link HashMap} composed of keys and values from both {@link Iterator}s
	 */
	public static > M pair(Iterable keys, Iterable values, Supplier target) {
		UntilBothIterator bothIterator = new UntilBothIterator<>(keys, values);
		return map(() -> bothIterator, Duo::getLeft, Duo::getRight, target);
	}
	
	/**
	 * Creates an {@link Iterable} by applying a mapping function on each element of the {@link Iterator} given by
	 * the source {@link Iterable}.
	 *
	 * @param iterable the {@link Iterable} that gives the source of the {@link Iterator} on which the mapping function will be applied
	 * @param mapper the mapping function of each element
	 * @return an {@link Iterable} which {@link Iterator} is one backed by given {@link Iterable} {@link Iterator} combined with mapping function
	 * @param  {@link Iterable} {@link Iterator} source element type
	 * @param  {@link Iterable} {@link Iterator} result element type
	 * @see #mappingIterator(Iterator, Function)
	 */
	public static  Iterable mappingIterator(Iterable iterable, Function mapper) {
		return () -> mappingIterator(iterable.iterator(), mapper);
	}
	
	/**
	 * Creates an {@link Iterator} by applying a mapping function on each element of the given {@link Iterator}.
	 *
	 * @param iterator the {@link Iterator} on which the mapping function will be applied
	 * @param mapper the mapping function of each element
	 * @return an {@link Iterator} backed by given {@link Iterator} combined with mapping function
	 * @param  {@link Iterator} source element type
	 * @param  {@link Iterator} result element type
	 * @see #mappingIterator(Iterable, Function)
	 */
	public static  Iterator mappingIterator(Iterator iterator, Function mapper) {
		return new Iterator() {
			@Override
			public boolean hasNext() {
				return iterator.hasNext();
			}
			
			@Override
			public O next() {
				return mapper.apply(iterator.next());
			}
		};
	}
	
	/**
	 * Converts an {@link Iterator} to a {@link Stream}.
	 * If the {@link Iterator} comes from a {@link Collection}, then prefer usage of {@link Collection#stream()}
	 * 
	 * @param iterator an {@link Iterator}, not null
	 * @return a {@link Stream} than will iterate over the {@link Iterator} passed as argument
	 */
	public static  Stream stream(Iterator iterator) {
		return stream(() -> iterator);
	}
	
	/**
	 * Converts an {@link Iterable} to a {@link Stream}.
	 * If the {@link Iterable} is a {@link Collection}, then prefer usage of {@link Collection#stream()}
	 * 
	 * @param iterable an {@link Iterable}, not null
	 * @return a {@link Stream} than will iterate over the {@link Iterable} passed as argument
	 */
	public static  Stream stream(Iterable iterable) {
		// StreamSupport knows how to convert an Iterable to a stream
		return (Stream) StreamSupport.stream(iterable.spliterator(), false);
	}
	
	/**
	 * Concatenates given {@link Iterable}s as a single one to have a linear view of all of them.
	 *
	 * @param iterables some {@link Iterable}s
	 * @return a {@link Iterable} than will iterate over all given {@link Iterable}s
	 */
	public static  Iterator concat(Iterable... iterables) {
		return new IteratorIterator<>(iterables);
	}
	
	/**
	 * Concatenates given {@link Iterator}s as a single one to have a linear view of all of them.
	 *
	 * @param iterators some {@link Iterator}s
	 * @return a {@link Iterator} than will iterate over all given {@link Iterator}s
	 */
	public static  Iterator concat(Iterator... iterators) {
		return new IteratorIterator<>(iterators);
	}
	
	/**
	 * Wraps given array of elements into an {@link Iterator}.
	 *
	 * @param elements elements to be iterated
	 * @return an {@link Iterator} of given elements
	 * @param  the elements type
	 */
	@SafeVarargs // method body doesn't handle improperly varargs parameter so it would generate ClassCastException
	public static  Iterator ofElements(E... elements ) {
		return new ArrayIterator<>(elements);
	}
	
	/**
	 * Consumes an {@link Iterable} with an action that gets index of each element of the {@link Iterable}
	 * 
	 * @param iterable any {@link Iterable}
	 * @param action an action that needs index and element as argument
	 * @param  {@link Iterable} elements type
	 */
	public static  void iterate(Iterable iterable, BiConsumer action) {
		int i = 0;
		for (E e : iterable) {
			action.accept(i++, e);
		}
	}
	
	/**
	 * Consumes an {@link Iterator} with an action that gets index of each element of the {@link Iterator}
	 * 
	 * @param iterator any {@link Iterator}
	 * @param action an action that needs index and element as argument
	 * @param  {@link Iterator} elements type
	 */
	public static  void iterate(Iterator iterator, BiConsumer action) {
		iterate(() -> iterator, action);
	}
	
	/**
	 * Keep elements of an {@link Iterable} that doesn't match a {@link Predicate}
	 * 
	 * @param iterable any {@link Iterable}
	 * @param includer any {@link Predicate}
	 * @param  type of elements
	 * @return given iterable without elements that doesn't match the given predicate
	 */
	public static  Iterable filter(Iterable iterable, Predicate includer) {
		return () -> filter(iterable.iterator(), includer);
	}
	
	/**
	 * Keep elements of an {@link Iterator} that doesn't match a {@link Predicate}
	 *
	 * @param iterator any {@link Iterator}
	 * @param includer any {@link Predicate}
	 * @param  type of elements
	 * @return given iterator without elements that doesn't match the given predicate
	 */
	public static  Iterator filter(Iterator iterator, Predicate includer) {
		return new Iterator() {
			
			private boolean hasNext = true;
			private E currentItem = null;
			private final Iterator surrogate = iterator;
			
			private void lookAhead() {
				while (hasNext = surrogate.hasNext()) {
					E item = surrogate.next();
					if (!includer.test(item)) {
						hasNext = false;
						currentItem = null;
					} else {
						hasNext = true;
						currentItem = item;
					}
					if (hasNext) {
						break;
					}
				}
			}
			
			@Override
			public boolean hasNext() {
				lookAhead();
				return hasNext;
			}
			
			@Override
			public E next() {
				if (!hasNext) {
					// this is necessary to be compliant with Iterator#next(..) contract
					throw new NoSuchElementException();
				}
				return currentItem;
			}
			
			@Override
			public void remove() {
				surrogate.remove();
			}
		};
	}
	
	/**
	 * Finds the first predicate-matching element into an {@link Iterable}
	 *
	 * @param iterable the {@link Iterable} to scan
	 * @param predicate the test to execute for equality
	 * @param  input type
	 * @return null if no element matches the predicate
	 */
	public static  I find(Iterable iterable, Predicate predicate) {
		return find(iterable.iterator(), predicate);
	}
	
	/**
	 * Finds the first predicate-matching element into an {@link Iterator}
	 *
	 * @param iterator the {@link Iterator} to scan
	 * @param predicate the test to execute for equality
	 * @param  input type
	 * @return null if no element matches the predicate
	 */
	@SuppressWarnings("squid:AssignmentInSubExpressionCheck" /* simple algorithm, doesn't require to extract the assignment from if */)
	public static  I find(Iterator iterator, Predicate predicate) {
		I result = null;
		boolean found = false;
		while (iterator.hasNext() && !found) {
			I step = iterator.next();
			if (found = predicate.test(step)) {
				result = step;
			}
		}
		return result;
	}
	
	/**
	 * Finds the first predicate-matching element (according to mapper) into the {@link Iterator} of an {@link Iterable}
	 *
	 * @param iterable the {@link Iterable} to scan
	 * @param mapper the mapper to extract the value to test
	 * @param predicate the test to execute for equality
	 * @param  input type
	 * @param  output type
	 * @return null if no mapped values matches the predicate
	 */
	public static  Duo find(Iterable iterable, Function mapper, Predicate predicate) {
		return find(iterable.iterator(), mapper, predicate);
	}
	
	/**
	 * Finds the first predicate-matching element (according to mapper) into an {@link Iterator}
	 * 
	 * @param iterator the {@link Iterator} to scan
	 * @param mapper the mapper to extract the value to test
	 * @param predicate the test to execute for equality
	 * @param  input type
	 * @param  output type
	 * @return null if no mapped values matches the predicate
	 */
	@SuppressWarnings("squid:AssignmentInSubExpressionCheck" /* simple algorithm, doesn't require to extract the assignment from if */)
	public static  Duo find(Iterator iterator, Function mapper, Predicate predicate) {
		Duo result = null;
		boolean found = false;
		while (iterator.hasNext() && !found) {
			I step = iterator.next();
			O mapperResult = mapper.apply(step);
			// voluntary variable reassignment
			//noinspection ReassignedVariable
			if (found = predicate.test(mapperResult)) {
				result = new Duo<>(step, mapperResult);
			}
		}
		return result;
	}
	
	/**
	 * Consumes all predicate-matching elements of an {@link Iterable}
	 *
	 * @param iterable the {@link Iterable} to scan
	 * @param matcher the test to execute for equality
	 * @param foundConsumer will be called with every matching element and its index
	 * @param  input type
	 */
	public static  void consume(Iterable iterable, Predicate matcher, BiConsumer foundConsumer) {
		consume(iterable.iterator(), matcher, foundConsumer);
	}
	
	/**
	 * Consumes all predicate-matching elements of an {@link Iterator}
	 *
	 * @param iterator the {@link Iterator} to scan
	 * @param matcher the test to execute for equality
	 * @param foundConsumer will be called with every matching element and its index
	 * @param  input type
	 */
	public static  void consume(Iterator iterator, Predicate matcher, BiConsumer foundConsumer) {
		int index = 0;
		while (iterator.hasNext()) {
			E step = iterator.next();
			if (matcher.test(step)) {
				foundConsumer.accept(step, index);
			}
			index++;
		}
	}
	
	/**
	 * Equivalent of {@link #consume(Iterator, Predicate, BiConsumer)} with a {@link Stream} as input
	 * 
	 * @param stream the {@link Stream} to scan
	 * @param matcher the test to execute for equality
	 * @param foundConsumer will be called with every matching element and its index
	 * @param  input type
	 */
	public static  void consume(Stream stream, Predicate matcher, BiConsumer foundConsumer) {
		final ModifiableInt index = new ModifiableInt(-1);
		stream.map(e -> new Duo<>(e, index.increment()))
				.filter(d -> matcher.test(d.getLeft()))
				.forEach(d -> foundConsumer.accept(d.getLeft(), d.getRight()));
	}
	
	/**
	 * Indicates if an {@link Iterator} contains a predicate-matching element
	 *
	 * @param iterator the {@link Iterator} to scan
	 * @param  input type
	 * @return true if a value that matches the {@link Predicate} is found
	 */
	public static  boolean contains(Iterator iterator, Predicate predicate) {
		return find(iterator, predicate) != null;
	}
	
	/**
	 * Indicates if an {@link Iterable} contains a predicate-matching element
	 *
	 * @param iterable the {@link Iterable} to scan
	 * @param  input type
	 * @return true if a value that matches the {@link Predicate} is found
	 */
	public static  boolean contains(Iterable iterable, Predicate predicate) {
		return find(iterable.iterator(), predicate) != null;
	}
	
	/**
	 * Indicates if an {@link Iterator} contains a predicate-matching element after applying a mapper to the elements
	 *
	 * @param iterator the {@link Iterator} to scan
	 * @param mapper the mapper to extract the value to test
	 * @param  input type
	 * @param  output type
	 * @return true if a value that matches the {@link Predicate} is found
	 */
	public static  boolean contains(Iterator iterator, Function mapper, Predicate predicate) {
		return find(iterator, mapper, predicate) != null;
	}
	
	/**
	 * Indicates if an {@link Iterable} contains a predicate-matching element after applying a mapper to the elements
	 *
	 * @param iterable the {@link Iterable} to scan
	 * @param mapper the mapper to extract the value to test
	 * @param  input type
	 * @param  output type
	 * @return true if a value that matches the {@link Predicate} is found
	 */
	public static  boolean contains(Iterable iterable, Function mapper, Predicate predicate) {
		return find(iterable.iterator(), mapper, predicate) != null;
	}
	
	public static  Iterator reverseIterator(List list) {
		return new ReverseListIterator<>(list);
	}
	
	/**
	 * Creates an {@link Iterator} in reverse order of the given {@link AbstractCollection}. Be aware that it requires to create a copy of the
	 * collection as an array, hence the collection can't be modified by the resulting iterator so it is {@link ReadOnlyIterator}.
	 * 
	 * @param collection any (non null) {@link AbstractCollection}
	 * @param  collection elements type
	 * @return a reverse-order {@link Iterator} of the given collection
	 */
	public static  ReadOnlyIterator reverseIterator(AbstractCollection collection) {
		return new ReverseArrayIterator<>((E[]) collection.toArray());
	}
	
	private Iterables() {}
}