blob: 2fc2ef592a425fafae5a2b524848b3b56bc682f4 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.toolbox;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Tools of functional programming, the notorious half of implementation of Lisp. (And sometimes a shard or two of Haskell.)
* User: dcheryasov
* Date: Nov 6, 2009 10:06:50 AM
*/
public class FP {
private FP() {
// do not instantiate
}
/**
* [a, b,..] -> [lambda(a), lambda(b),...]
* Action is lazy: function is applied to source items only as soon as values are extracted from the resulting iterable.
* @param lambda function to apply
* @param source list to process
* @return list of mapped values.
*/
@NotNull
public static <S, R> Iterable<R> map(@NotNull final Lambda1<S, R> lambda, @NotNull final Iterable<S> source) {
return new Iterable<R>() {
final Iterator<S> feeder = source.iterator();
public Iterator<R> iterator() {
return new Iterator<R>() {
public boolean hasNext() {
return feeder.hasNext();
}
public R next() {
return lambda.apply(feeder.next());
}
public void remove() {
throw new UnsupportedOperationException("Cannot remove from map()");
}
};
}
};
}
/**
* Convenience form of {@link #map(Lambda1, Iterable)}.
*/
@NotNull
public static <S, R> Iterable<R> map(@NotNull final Lambda1<S, R> lambda, @NotNull final S[] source) {
return map(lambda, Arrays.asList(source));
}
/**
* Same as {@link #map}, but non-lazy an returns a modifiable List.
*/
public static <S, R> List<R> mapList(@NotNull final Lambda1<S, R> lambda, @NotNull final Iterable<S> source) {
List<R> ret = new ArrayList<R>(source instanceof Collection? ((Collection)source).size() : 10);
for (R what : map(lambda, source)) ret.add(what);
return ret;
}
/**
* Apply a two-argument lambda to each sequence element and an accumulator.
* @param lambda function to apply; accumulator is the first parameter, sequence item is the second
* @param source sequence to process
* @param unit initial value of the accumulator (like the initial 0 for summing)
* @param <ItemT> type of items in the list
* @param <AccT> 'accumulator' type; can be same as ItemT, or reasonably different (consider ItemT=String and AccT=StringBuilder)
* @return value of the accumulator after all the list is processed.
*/
public static <AccT, ItemT> AccT fold(@NotNull Lambda2<AccT, ItemT, AccT> lambda, @NotNull Iterable<ItemT> source, @NotNull final AccT unit) {
AccT ret = unit;
for (ItemT item : source) ret = lambda.apply(ret, item);
return ret;
}
/**
* Same as fold(), but the arguments of lambda are reversed: the accumulator is the second (right) parameter.
* @param lambda function to apply; sequence item is the first parameter, accumulator is the second
* @param source sequence to process
* @param unit initial value of the accumulator (like the initial 0 for summing)
* @param <ItemT> type of items in the list
* @param <AccT> 'accumulator' type
* @return value of the accumulator after all the list is processed.
*/
public static <AccT, ItemT> AccT foldr(@NotNull Lambda2<ItemT, AccT, AccT> lambda, @NotNull Iterable<ItemT> source, @NotNull final AccT unit) {
AccT ret = unit;
for (ItemT item : source) ret = lambda.apply(item, ret);
return ret;
}
/**
* Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..].
* If sequences are of different length, uses up the shortest of the sequences; the rest of the longer sequence is unused.
* The action is lazy: either iterable is only accessed as many times as the result.
* @param one source of first elements
* @param two source of second elements, possibly shorter
* @return list of pairs of elements
*/
public static <R1, R2> Iterable<Pair<R1, R2>> zip(Iterable<R1> one, Iterable<R2> two) {
return zipInternal(one, two, null, null, false, false);
}
/**
* Same as {@link #zip(Iterable, Iterable)}, but non-lazy and returns a modifiable List.
*/
public static <R1, R2> List<Pair<R1, R2>> zipList(Iterable<R1> one, Iterable<R2> two) {
List<Pair<R1, R2>> ret = new ArrayList<Pair<R1, R2>>(proposeZippedListLength(one, two, false, false));
for (Pair<R1, R2>what : zipInternal(one, two, null, null, false, false)) ret.add(what);
return ret;
}
/**
* Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..]. Fills missing second elements with filler.
* Always uses up entire sequence one; if sequence two is longer, part of it is unused.
* The action is lazy: either iterable is only accessed as many times as the result.
* @param one source of first elements
* @param two source of second elements, possibly shorter
* @param filler value to use instead of elements of sequence two if it is shorter than sequence one
* @return list of pairs of elements
*/
public static <R1, R2> Iterable<Pair<R1, R2>> zip(Iterable<R1> one, Iterable<R2> two, R2 filler) {
return zipInternal(one, two, null, filler, false, true);
}
/**
* Same as {@link #zip(Iterable, Iterable, Object)}, but non-lazy and returns a modifiable List.
*/
public static <R1, R2> List<Pair<R1, R2>> zipList(Iterable<R1> one, Iterable<R2> two, R2 filler) {
List<Pair<R1, R2>> ret = new ArrayList<Pair<R1, R2>>(proposeZippedListLength(one, two, false, true));
for (Pair<R1, R2>what : zipInternal(one, two, null, filler, false, true)) ret.add(what);
return ret;
}
/**
* Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..]. Fills all missing elements with filler.
* Always uses up both sequences, using the appropriate filler for elements of the shorter sequences.
* The action is lazy: either iterable is only accessed as many times as the result.
* @param one sequences of first elements
* @param two sequences of second elements, possibly shorter
* @param filler1 value to use instead of elements of sequences one if it is shorter than list two
* @param filler2 value to use instead of elements of sequences two if it is shorter than list one
* @return list of pairs of elements
*/
public static <R1, R2> Iterable<Pair<R1, R2>> zip(Iterable<R1> one, Iterable<R2> two, R1 filler1, R2 filler2) {
return zipInternal(one, two, filler1, filler2, true, true);
}
/**
* Same as {@link #zip(Iterable, Iterable, Object, Object)}, but non-lazy and returns a modifiable List.
*/
public static <R1, R2> List<Pair<R1, R2>> zipList(Iterable<R1> one, Iterable<R2> two, R1 filler1, R2 filler2) {
List<Pair<R1, R2>> ret = new ArrayList<Pair<R1, R2>>(proposeZippedListLength(one, two, true, true));
for (Pair<R1, R2>what : zipInternal(one, two, filler1, filler2, true, true)) ret.add(what);
return ret;
}
/**
* Tries to determine the size of an array list of exactly the right size to accommodate
* the result of {@link #zip(Iterable, Iterable, Object, Object)}.
* @param one first iterbale
* @param two second iterable
* @param fill1 true if padding of iterable one is required
* @param fill2 true if padding of iterable two is required
* @return size, if it can be determined, or 10 (which is the default size of ArrayList).
*/
public static int proposeZippedListLength(Iterable one, Iterable two, boolean fill1, boolean fill2) {
int size1 = 0;
int size2 = 0;
int approx_size = 0;
if (one instanceof List) size1 = ((List)one).size();
if (two instanceof List) size2 = ((List)two).size();
if (fill1 && fill2) approx_size = Math.max(size1, size2);
if (!fill1 && !fill2) approx_size = Math.min(size1, size2);
if (fill1 && !fill2) approx_size = size1;
if (!fill1 && fill2) approx_size = size2;
if (approx_size == 0) approx_size = 10;
return approx_size;
}
/**
* Zips two lists.
* @param one first elements
* @param two second elements
* @param filler1 to fill missing first elements
* @param filler2 to fill missing second elements
* @param fill1 use filler1 if list one is too short
* @param fill2 use filler2 if list two is too short
* @return zipped list
*/
private static <R1, R2> Iterable<Pair<R1, R2>> zipInternal(
Iterable<R1> one, Iterable<R2> two, final R1 filler1, final R2 filler2, final boolean fill1, final boolean fill2
) {
final Iterator<R1> one_iter = one.iterator();
final Iterator<R2> two_iter = two.iterator();
return new Iterable<Pair<R1, R2>>() {
public Iterator<Pair<R1, R2>> iterator() {
return new Iterator<Pair<R1, R2>>() {
public void remove() {
throw new UnsupportedOperationException("Cannot remove from zip()");
}
public boolean hasNext() {
final boolean one_has = one_iter.hasNext();
final boolean two_has = two_iter.hasNext();
return (
one_has && two_has ||
fill1 && two_has ||
fill2 && one_has
);
}
public Pair<R1, R2> next() {
if (one_iter.hasNext() && two_iter.hasNext()) return Pair.create(one_iter.next(), two_iter.next());
if (fill1 && two_iter.hasNext()) return Pair.create(filler1, two_iter.next());
if (fill2 && one_iter.hasNext()) return Pair.create(one_iter.next(), filler2);
throw new NoSuchElementException();
}
};
}
};
}
/**
* Combines two functions so that the second is applied to the result of the first.
* @param f first (inner) function
* @param g second (outer) function
* @return their combination, f o g
*/
public static <A1, R1, R2> Lambda1<A1, R2> combine(final Lambda1<A1, R1> f, final Lambda1<R1, R2> g) {
return new Lambda1<A1, R2>() {
public R2 apply(A1 arg) {
return g.apply(f.apply(arg));
}
};
}
// TODO: add slices, array wrapping %)
/**
* One-argument lambda (function).
*/
public interface Lambda1<A, R> {
R apply(A arg);
}
/**
* Two-arguments lambda (function).
*/
public interface Lambda2<A1, A2, R> {
R apply(A1 arg1, A2 arg2);
}
/**
* Useful for {@link FP#fold(Lambda2, Iterable, Object) fold}ing into a string. Element's {@code .toString()} is appended to the string builder.
*/
public static class StringCollector<T> implements FP.Lambda2<StringBuilder, T, StringBuilder> {
public StringBuilder apply(StringBuilder builder, T arg2) {
if (arg2 == null) {
throw new IllegalArgumentException("Null item in list of strings to concatenate. Text so far: " + builder.toString());
}
return builder.append(arg2.toString());
}
}
}