package org.enumerable.lambda.enumerable.collection;
import static java.util.Collections.*;
import static org.enumerable.lambda.Fn0.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.Set;
import java.util.regex.Pattern;
import org.enumerable.lambda.Fn0;
import org.enumerable.lambda.Fn1;
import org.enumerable.lambda.Fn2;
import org.enumerable.lambda.enumerable.Enumerable;
import org.enumerable.lambda.enumerable.EnumerableArrays;
/**
* The Enumerable mixin provides collection classes with several traversal and
* searching methods, and with the ability to sort. The class must provide a
* method {@link Iterable#iterator()}, which yields successive members of the
* collection. If {@link #max}, {@link #min}, or {@link #sort} is used, the
* objects in the collection must also implement a meaningful
* {@link Comparable#compareTo(Object)} operator, as these methods rely on an
* ordering between members of the collection.
* <p>
* It can be mixed in either by using the decorators ({@link EIterable},
* {@link ECollection}, {@link EList}, {@link ESet} and {@link EMap}) or by
* subclassing and provide {@link Iterable#iterator()}.
* <p>
* The static methods in the facades {@link Enumerable} and
* {@link EnumerableArrays} use this implementation via the decorators.
*/
public abstract class EnumerableModule<E> implements IEnumerable<E> {
@SuppressWarnings("unchecked")
public static <T, R extends EnumerableModule<T>> R extend(Iterable<T> iterable) {
if (iterable instanceof EnumerableModule<?>)
return (R) iterable;
if (iterable instanceof List<?>)
return (R) new EList<T>((List<T>) iterable);
if (iterable instanceof Set<?>)
return (R) new ESet<T>((Set<T>) iterable);
if (iterable instanceof Collection<?>)
return (R) new ECollection<T>((Collection<T>) iterable);
return (R) new EIterable<T>(iterable);
}
public static <K, V> EMap<K, V> extend(Map<K, V> map) {
if (map instanceof EMap<?, ?>)
return (EMap<K, V>) map;
return new EMap<K, V>(map);
}
public boolean all(Fn1<? super E, ?> block) {
for (E each : this)
if (isFalseOrNull(block.call(each)))
return false;
return true;
}
public boolean all() {
return all(Fn1.identity());
}
public boolean any(Fn1<? super E, ?> block) {
for (E each : this)
if (isNotFalseOrNull(block.call(each)))
return true;
return false;
}
public boolean any() {
return any(Fn1.identity());
}
public <R> EList<R> collect(Fn1<? super E, ? extends R> block) {
EList<R> result = new EList<R>();
for (E each : this)
result.add(block.call(each));
return result;
}
@SuppressWarnings({ "unchecked", "unused" })
public int count() {
if (this instanceof Collection<?>)
return ((Collection<E>) this).size();
int count = 0;
for (E each : this)
count++;
return count;
}
@SuppressWarnings("unchecked")
public int count(E obj) {
if (obj instanceof Fn1<?, ?>)
return count((Fn1<E, Boolean>) obj);
int count = 0;
if (obj == null) {
for (E each : this)
if (each == null)
count++;
} else {
for (E each : this)
if (obj.equals(each))
count++;
}
return count;
}
@SuppressWarnings("unchecked")
public int count(Fn1<? super E, Boolean> block) {
if (block == null)
return count((E) block);
return select(block).size();
}
public <R> EList<E> cycle(Fn1<? super E, R> block) {
while (true)
for (E each : this)
block.call(each);
}
public <R> EList<E> cycle(int times, Fn1<? super E, R> block) {
if (times <= 0)
return null;
EList<E> list = new EList<E>();
for (E each : this) {
block.call(each);
list.add(each);
}
EList<E> result = new EList<E>();
while (times-- > 0)
result.addAll(list);
return result;
}
public E detect(Fn1<? super E, Boolean> block) {
return detect(null, block);
}
public E detect(Fn0<E> ifNone, Fn1<? super E, Boolean> block) {
for (E each : this)
if (isNotFalseOrNull(block.call(each)))
return each;
return ifNone == null ? null : ifNone.call();
}
public EList<E> drop(int n) {
if (n < 0)
throw new IllegalArgumentException("attempt to drop negative size");
EList<E> result = new EList<E>();
for (E each : this)
if (n-- <= 0)
result.add(each);
return result;
}
public EList<E> dropWhile(Fn1<? super E, Boolean> block) {
EList<E> result = new EList<E>();
for (E next : this)
if (!result.isEmpty() || isFalseOrNull(block.call(next)))
result.add(next);
return result;
}
public <R> EnumerableModule<E> each(Fn1<? super E, R> block) {
for (E each : this)
block.call(each);
return this;
}
public <R> Object eachCons(int n, Fn1<List<E>, R> block) {
if (n <= 0)
throw new IllegalArgumentException("invalid size");
List<E> list = new ArrayList<E>();
for (E each : this) {
list.add(each);
if (list.size() == n) {
block.call(list);
list = new ArrayList<E>(list.subList(1, list.size()));
}
}
return null;
}
public <R> Object eachSlice(int n, Fn1<List<E>, R> block) {
if (n <= 0)
throw new IllegalArgumentException("invalid size");
List<E> list = new ArrayList<E>();
for (E each : this) {
list.add(each);
if (list.size() == n) {
block.call(list);
list = new ArrayList<E>();
}
}
if (!list.isEmpty())
block.call(list);
return null;
}
public <R> EnumerableModule<E> eachWithIndex(Fn2<? super E, Integer, R> block) {
int i = 0;
for (E each : this)
block.call(each, i++);
return this;
}
public <M, R> M eachWithObject(M memo, Fn2<? super E, M, R> block) {
for (E each : this)
block.call(each, memo);
return memo;
}
public EList<E> entries() {
return toList();
}
public E find(Fn1<? super E, Boolean> block) {
return detect(block);
}
public E find(Fn0<E> ifNone, Fn1<? super E, Boolean> block) {
return detect(ifNone, block);
}
public EList<E> findAll(Fn1<? super E, Boolean> block) {
EList<E> result = new EList<E>();
for (E each : this)
if (isNotFalseOrNull(block.call(each)))
result.add(each);
return result;
}
@SuppressWarnings("unchecked")
public int findIndex(Fn1<? super E, Boolean> block) {
if (block == null)
return findIndex((E) block);
int index = 0;
for (E each : this)
if (isNotFalseOrNull(block.call(each)))
return index;
else
index++;
return -1;
}
@SuppressWarnings({ "serial", "unchecked" })
public int findIndex(final E obj) {
if (obj instanceof Fn1<?, ?>)
return findIndex((Fn1<E, Boolean>) obj);
return findIndex(new Fn1<E, Boolean>() {
public Boolean call(E a1) {
if (obj == null)
return a1 == null;
return obj.equals(a1);
}
});
}
public E first() {
for (E each : this)
return each;
return null;
}
public EList<E> first(int n) {
return take(n);
}
public EList<E> grep(Pattern pattern) {
return grep(pattern, Fn1.<E> identity());
}
public <R> EList<R> grep(Pattern pattern, Fn1<? super E, R> block) {
EList<R> result = new EList<R>();
for (E each : this)
if (pattern.matcher(each.toString()).matches())
result.add(block.call(each));
return result;
}
public EList<E> grep(String pattern) {
return grep(Pattern.compile(pattern));
}
public <R> EList<R> grep(String pattern, Fn1<? super E, R> block) {
return grep(Pattern.compile(pattern), block);
}
public <R> EMap<R, EList<E>> groupBy(Fn1<? super E, R> block) {
EMap<R, EList<E>> result = new EMap<R, EList<E>>(new LinkedHashMap<R, EList<E>>());
for (E each : this) {
R key = block.call(each);
if (!result.containsKey(key))
result.put(key, new EList<E>());
result.get(key).add(each);
}
return result;
}
public boolean include(Object obj) {
if (obj == null) {
for (E each : this)
if (each == null)
return true;
} else {
for (E each : this)
if (obj.equals(each))
return true;
}
return false;
}
public E inject(Fn2<? super E, ? super E, ? extends E> block) {
Iterator<E> i = this.iterator();
if (!i.hasNext())
return null;
E initial = i.next();
while (i.hasNext())
initial = block.call(initial, i.next());
return initial;
}
public <R> R inject(R initial, Fn2<? super R, ? super E, ? extends R> block) {
for (E each : this)
initial = block.call(initial, each);
return initial;
}
public <R> EList<R> map(Fn1<? super E, ? extends R> block) {
return collect(block);
}
public E max() {
Comparator<E> reverseOrder = reverseOrder();
return minInternal(reverseOrder);
}
public E max(final Fn2<? super E, ? super E, Integer> block) {
return minInternal(reverseOrder(new Comparator<E>() {
public int compare(E o1, E o2) {
return ((Number) block.call(o1, o2)).intValue();
}
}));
}
public <R extends Object & Comparable<? super R>> E maxBy(Fn1<? super E, R> block) {
return minInternal(reverseOrder(new BlockResultComparator<E, R>(block)));
}
public boolean member(Object obj) {
return include(obj);
}
public E min() {
Comparator<E> naturalOrder = naturalOrder();
return minInternal(naturalOrder);
}
public E min(final Fn2<? super E, ? super E, Integer> block) {
return minInternal(new Comparator<E>() {
public int compare(E o1, E o2) {
return ((Number) block.call(o1, o2)).intValue();
}
});
}
public <R extends Object & Comparable<? super R>> E minBy(Fn1<? super E, R> block) {
return minInternal(new BlockResultComparator<E, R>(block));
}
public EList<E> minMax() {
EList<E> result = new EList<E>();
result.add(min());
result.add(max());
return result;
}
public EList<E> minMax(Fn2<? super E, ? super E, Integer> block) {
EList<E> result = new EList<E>();
result.add(min(block));
result.add(max(block));
return result;
}
public <R extends Object & Comparable<? super R>> EList<E> minMaxBy(Fn1<? super E, R> block) {
EList<E> result = new EList<E>();
result.add(minBy(block));
result.add(maxBy(block));
return result;
}
public boolean none(Fn1<? super E, ?> block) {
return !any(block);
}
public boolean none() {
return none(Fn1.identity());
}
public boolean one(Fn1<? super E, ?> block) {
Object match = null;
for (E each : this) {
Object result = block.call(each);
if (isNotFalseOrNull(result))
if (match != null)
return false;
else
match = result;
}
return match != null;
}
public boolean one() {
return one(Fn1.identity());
}
public EList<EList<E>> partition(Fn1<? super E, Boolean> block) {
EList<E> selected = new EList<E>();
EList<E> rejected = new EList<E>();
for (E each : this)
if (isNotFalseOrNull(block.call(each)))
selected.add(each);
else
rejected.add(each);
EList<EList<E>> result = new EList<EList<E>>();
result.add(selected);
result.add(rejected);
return result;
}
public E reduce(Fn2<? super E, ? super E, ? extends E> block) {
return inject(block);
}
public <R> R reduce(R initial, Fn2<? super R, ? super E, ? extends R> block) {
return inject(initial, block);
}
public EList<E> reject(Fn1<? super E, Boolean> block) {
EList<E> result = new EList<E>();
for (E each : this)
if (isFalseOrNull(block.call(each)))
result.add(each);
return result;
}
public <R> EnumerableModule<E> reverseEach(Fn1<? super E, R> block) {
List<E> result = asNewList();
Collections.reverse(result);
new EList<E>(result).each(block);
return this;
}
public EList<E> select(Fn1<? super E, Boolean> block) {
return findAll(block);
}
public EList<E> sort() {
return sortInternal((Comparator<E>) null);
}
public EList<E> sort(final Fn2<? super E, ? super E, Integer> block) {
return sortInternal(new Comparator<E>() {
public int compare(E o1, E o2) {
return ((Number) block.call(o1, o2)).intValue();
}
});
}
public <R extends Object & Comparable<? super R>> EList<E> sortBy(final Fn1<? super E, R> block) {
return sortBySchwartzianTransform(block);
}
public EList<E> take(int n) {
if (n < 0)
throw new IllegalArgumentException("attempt to take negative size");
EList<E> result = new EList<E>();
for (Iterator<E> i = iterator(); n != 0 && i.hasNext(); n--)
result.add(i.next());
return result;
}
public EList<E> takeWhile(Fn1<? super E, Boolean> block) {
EList<E> result = new EList<E>();
for (E next : this)
if (isNotFalseOrNull(block.call(next)))
result.add(next);
else
return result;
return result;
}
public EList<E> toList() {
return new EList<E>(asNewList());
}
public ESet<E> toSet() {
return new ESet<E>(new HashSet<E>(asNewList()));
}
public <R> ESet<R> toSet(Fn1<? super E, R> block) {
return new ESet<R>(new HashSet<R>(collect(block)));
}
public EList<EList<?>> zip() {
return zip(new Iterable<?>[0]);
}
public <R> Object zip(List<Iterable<?>> args, Fn1<? super EList<?>, R> block) {
zip(args.toArray(new Iterable<?>[0])).each(block);
return null;
}
public EList<EList<?>> zip(Iterable<?>... args) {
EList<EList<?>> allResults = new EList<EList<?>>();
List<Iterator<?>> iterators = new ArrayList<Iterator<?>>();
iterators.add(this.iterator());
for (Iterable<?> iterable : args)
iterators.add(iterable.iterator());
while (iterators.get(0).hasNext()) {
EList<Object> result = new EList<Object>();
for (Iterator<?> iterator : iterators)
if (iterator.hasNext())
result.add(iterator.next());
else
result.add(null);
allResults.add(result);
}
return allResults;
}
@SuppressWarnings("unchecked")
List<E> asNewList() {
if (this instanceof Collection<?>)
return new ArrayList<E>((Collection<E>) this);
List<E> result = new ArrayList<E>();
for (E each : this)
result.add(each);
return result;
}
E minInternal(Comparator<? super E> comparator) {
E result = null;
for (E each : this)
if (result == null || comparator.compare(each, result) < 0)
result = each;
return result;
}
EList<E> sortInternal(Comparator<? super E> comparator) {
List<E> result = asNewList();
Collections.sort(result, comparator);
return new EList<E>(result);
}
class Pair<R extends Object & Comparable<? super R>> implements Comparable<Pair<R>> {
R key;
E value;
Pair(R key, E value) {
this.key = key;
this.value = value;
}
public int compareTo(Pair<R> o) {
return key.compareTo(o.key);
}
}
@SuppressWarnings("unchecked")
<R extends Object & Comparable<? super R>> EList<E> sortBySchwartzianTransform(Fn1<? super E, R> block) {
EList<Pair<R>> pairs = new EList<Pair<R>>();
for (E each : this)
pairs.add(new Pair<R>(block.call(each), each));
Collections.sort(pairs);
EList<E> result = (EList<E>) pairs;
for (int i = 0; i < pairs.size(); i++)
result.set(i, pairs.get(i).value);
return result;
}
static class BlockResultComparator<E, R extends Object & Comparable<? super R>> implements Comparator<E> {
Fn1<? super E, R> block;
BlockResultComparator(Fn1<? super E, R> block) {
this.block = block;
}
public int compare(E o1, E o2) {
return block.call(o1).compareTo(block.call(o2));
}
}
static class CachedBlockResultComparator<E, R extends Object & Comparable<? super R>> implements Comparator<E> {
Map<E, R> cache = new HashMap<E, R>();
Fn1<? super E, R> block;
CachedBlockResultComparator(Fn1<? super E, R> block) {
this.block = block;
}
R cached(E e) {
if (!cache.containsKey(e))
cache.put(e, block.call(e));
return cache.get(e);
}
public int compare(E o1, E o2) {
return cached(o1).compareTo(cached(o2));
}
}
static final NaturalOrderComparator NATURAL_ORDER = new NaturalOrderComparator();
static class NaturalOrderComparator implements Comparator<Comparable<Object>> {
public int compare(Comparable<Object> o1, Comparable<Object> o2) {
return o1.compareTo(o2);
}
}
@SuppressWarnings("unchecked")
Comparator<E> naturalOrder() {
return (Comparator<E>) NATURAL_ORDER;
}
}