Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compose array of Functions using stream

I want to compose an array of functions to one function. Given multiple functions the method should return a function which is the composition of the input functions.

One way to do this is

public static <T> Function<T,T> composeAll(Function<T,T>... functions){
    Function<T,T> res = Function.identity();
    for(Function<T,T> f : functions){
       res = res.compose(f);
    }
    return res;
}

I'm looking to achieve the same result by first making a stream of the array of functions. But I can't figure out how to do this

public static <T> Function<T,T> composeAll2 (Function<T,T>... functions){
    Function<T,T> res = Function.identity();
    Arrays.stream(functions). ??? 
    return res;
}

What code should I enter in the last method where I put question marks now?

like image 331
xtra Avatar asked Dec 11 '17 14:12

xtra


2 Answers

You can make use of the Stream#reduce() operation:

public static <T> Function<T,T> composeAll2 (Function<T,T>... functions){
    return Arrays.stream(functions).reduce(Function.identity(), Function::compose);
}

Which is identical to your iterative way of composing functions.

like image 142
Lino Avatar answered Oct 12 '22 23:10

Lino


While @Lino's answer is concise, it is unsafe and prone to stack overflow:

List<Function<Integer, Integer>> list = Collections.nCopies(10000, i -> i + 1);
System.out.println(composeAll2(list.toArray(new Function[0])).apply(0));

If you run this code, it will fail with StackOverflowError:

Exception in thread "main" java.lang.StackOverflowError
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    at java.util.function.Function.lambda$compose$0(Function.java:68)
    ...

To avoid this, I suggest not using streams at all and using the old for-each loop:

public static <T> Function<T,T> composeAll2(Function<T,T>... functions){
    return input -> {
        T res = input;
        for (Function<T, T> f : functions) {
            res = f.apply(res);
        }
        return res;
    };
}

This version of composeAll2 will use a constant stack space.

like image 27
ZhekaKozlov Avatar answered Oct 12 '22 22:10

ZhekaKozlov