package church.util; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import church.lang.ByteStream; import church.primitives.Strings; public class CollectionsSupport { // Equals and Hashcode public interface EquivalenceRelation<T> { boolean equal(T a, T b); } public interface HashingFunction<T> { int hashCode(T a); } // Left and Right Folds. See: https://en.wikipedia.org/wiki/Fold_(higher-order_function) public interface LeftFold<C, T> { <R> R reduce(BiFunction<R, T, R> accumulator, R initialValue, C collection); } public interface RightFold<C, T> { <R> R reduce(BiFunction<T, R, R> accumulator, R initialValue, C collection); } public static <C0, C1, C2> LeftFold<C0, C2> leftLift(LeftFold<C1, C2> fold, Function<C0, C1> projection) { return new LeftFold<C0, C2>() { @Override public <R> R reduce(BiFunction<R, C2, R> accumulator, R initialValue, C0 collection) { return fold.reduce(accumulator, initialValue, projection.apply(collection)); } }; } public static <C0, C1, C2> RightFold<C0, C2> rightLift(RightFold<C1, C2> fold, Function<C0, C1> projection) { return new RightFold<C0, C2>() { @Override public <R> R reduce(BiFunction<C2, R, R> accumulator, R initialValue, C0 collection) { return fold.reduce(accumulator, initialValue, projection.apply(collection)); } }; } public static <C0, C1, C2, C3> LeftFold<C0, C3> compose(LeftFold<C1, C2> fold1, LeftFold<C2, C3> fold2, Function<C0, C1> projection) { return new LeftFold<C0, C3>() { @Override public <R> R reduce(BiFunction<R, C3, R> accumulator, R initialValue, C0 collection) { return fold1.reduce((result, bucket) -> fold2.reduce(accumulator, result, bucket), initialValue, projection.apply(collection)); } }; } public static <C0, C1, C2, C3> RightFold<C0, C3> compose(RightFold<C1, C2> fold1, RightFold<C2, C3> fold2, Function<C0, C1> projection) { return new RightFold<C0, C3>() { @Override public <R> R reduce(BiFunction<C3, R, R> accumulator, R initialValue, C0 collection) { return fold1.reduce((bucket, result) -> fold2.reduce(accumulator, result, bucket), initialValue, projection.apply(collection)); } }; } // Legacy support for the Java based Hash{Set/Map} public static <C, T> String toString2(LeftFold<C, T> leftFold, C collection, Function<T, String> stringer, String open, String separator, String close) { return leftFold.reduce(new BiFunction<StringBuilder, T, StringBuilder>() { boolean first = true; @Override public StringBuilder apply(StringBuilder prev, T e) { if (first) { first = false; // todo here we go again prev.append(open); } else { prev.append(separator); } return prev.append(stringer.apply(e)); } }, new StringBuilder(), collection).append(close).toString(); } // Legacy support for the Java based Hash{Set/Map} public static <C, T> String toString3(LeftFold<C, T> leftFold, C collection, Function<T, String> stringer) { return toString2(leftFold, collection, stringer, "{", ", ", "}"); } // todo rationalise public static <C, T> ByteStream encode2(ByteStream stream, LeftFold<C, T> leftFold, C collection, BiFunction<ByteStream, T, ByteStream> encoder, String open, String separator, String close) { return Strings.encode(leftFold.reduce(new BiFunction<ByteStream, T, ByteStream>() { boolean first = true; @Override public ByteStream apply(ByteStream prev, T e) { if (first) { first = false; // todo here we go again prev = Strings.encode(prev, open); } else { prev = Strings.encode(prev, separator); } return encoder.apply(prev, e); } }, stream, collection), close); } // todo rationalise public static <C, T> ByteStream encode3(ByteStream stream, LeftFold<C, T> leftFold, C collection, BiFunction<ByteStream, T, ByteStream> encoder) { return encode2(stream, leftFold, collection, encoder, "{", ", ", "}"); } // Couldn't get rid of null! public static <K, V> V get0(HashTable<Entry<K, V>, K> hashTable, K key, Function<K, V> defaultFactory) { LinkedList<Entry<K, V>> bucket = hashTable.getBucketForKey(key); Predicate<Entry<K, V>> selector = entry -> hashTable.equivalenceRelation.equal(key, entry.key); Entry<K, V> result = LinkedList.find(bucket, selector, null); return result == null ? defaultFactory.apply(key) : result.value; } public static final class AbsentKeyException extends RuntimeException { } public static <K, V> V getOrFail(HashTable<Entry<K, V>, K> hashTable, K key) { return get0(hashTable, key, k -> { throw new AbsentKeyException(); }); } }