4

Is it possible to do this using Predicate interface.

I have a client class that utilizes functions provided by a MathUtility class. Whatever the Mathmatical operation it should happen only within the MathUtility class.

    //in client 
    MathUtility.sum(listOfInts, (Integer i)->{return (i<3);});

   //in utility
    class MathUtility<T extends Number> {
        public static <T extends Number> T sumWithCondition(List<T> numbers, Predicate<T> condition) {
            return numbers.parallelStream()
                    .filter(condition)
                    .map(i -> i)
                    .reduce(0, T::sum); //compile time error
        }
        public static <T extends Number> T avgWithCondition(List<T> numbers, Predicate<T> condition) {
            //another function
        }
        //lot many functions go here
    }

Right now it fails with this error - The method reduce(T, BinaryOperator<T>) in the type Stream<T> is not applicable for the arguments (int, T::sum)

Note: I do not want to write sum functions for different Number types

EDIT: Detailed discussion on this topic covered in this Github Notebook

5
  • 3
    There is no such thing as T::sum, which is why it gives you this error. Commented Apr 15, 2017 at 17:27
  • So then basically I'll need to write a sum function to add T's. Is there a way to do it without writing a sum function for every possible type of T that i'm expecting. I mean let the jvm do it and throw error if it can't. Commented Apr 15, 2017 at 17:31
  • If T is an arbitrary type, the I'm afraid that'd be the only way. If T is a Number only, then you can sort of get by using mapToDouble and then sum(). But you should always be wary of custom Numbers such as BigDecimals, and you would only be able to return a double. So with seeing how much of a hassle it all is, why not scrap the whole such utility approach? It doesn't really add a lot of value in my eyes. Not in taking a sum part at least. Commented Apr 15, 2017 at 17:45
  • 2
    Can your sum method take in the reduction function as an argument? Commented Apr 15, 2017 at 17:47
  • @M.Prokhorov I'm trying to sum up only Numbers. I have changed the code above. Commented Apr 15, 2017 at 18:09

4 Answers 4

5

Is there a way to do it without writing a sum function for every possible type of T that i'm expecting?

As Aaron Davis stated in a comment above, you can pass the reduction parameters to the method itself.

public static <T> T sumWithCondition(List<T> numbers, Predicate<T> condition, T identity, BinaryOperator<T> accumulator) {
    return numbers.parallelStream().filter(condition).reduce(identity, accumulator);
}

An example would be:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

System.out.println(sumWithCondition(list, i -> i > 1, 0, (a, b) -> a + b));

>> 14

List<BigInteger> list2 = Arrays.asList(BigInteger.ONE, BigInteger.ONE);

System.out.println(sumWithCondition(list2, i -> true, BigInteger.ZERO, (a, b) -> a.add(b)));

>> 2
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. But I do not want the summing operation to come from the invoking side only the condition. I tried to do return numbers.parallelStream().filter(condition).reduce(0, (a,b)->a+b); But it doesn't work. T type doesn't know + operator.
@John Yeah, because it's not possible any other way, as Number can be a BigInteger, BigDecimal, etc. It is required that you specify how two Numbers can be summed, as the JDK does not implement that for every Number.
3
  1. you must point out which actual type of Number to be summed, Since the Number class has no static sum method.
  2. you must assign identity with type of T extends Number,0 is an concrete type of Integer and does not compatible with type of T.

Possible Solution

you can make which actual type of Number to be summed later, for example:

Integer sumToInt = MathUtility.sum(numbers, condition).as(Integer.class);
Double sumToDouble = MathUtility.sum(numbers, condition).as(Double.class);

OR you can make which actual type of Number to be summed ahead, when using this style you are free to take type of actual Number to every sum to be called, one the other hand, you can reuse it without taking any confused parameters and which is exactly what you want,for example:

SumOp<Integer> sumIntOp = SumOp.of(Integer.class);

//sumIntOp is reused twice.
Integer sumToInt1 = sumIntOp.sum(numbers1, condition1);
Integer sumToInt2 = sumIntOp.sum(numbers2, condition2);

MathUtility

class MathUtility {

    private static <T extends Number> Sum sum(List<T> numbers,
                                              Predicate<T> condition) {
        return sum(numbers.parallelStream().filter(condition));
    }

    private static <T extends Number> Sum sum(Stream<T> stream) {
        return new Sum() {
            public <T extends Number> T as(Class<T> type) {
                return SumOp.of(type).sum(stream);
            }
        };
    }

    interface Sum {
        <T extends Number> T as(Class<T> type);
    }
}

SumOp

public class SumOp<T extends Number> {
    private static final Map<Class<?>, SumOp<?>> OPERATORS = new HashMap<>();
    private final T identity;
    private final BinaryOperator<T> plusOp;
    private final Function<Number, T> valueExtractor;

    static {
       register(Integer.class, new SumOp<>(0, Integer::sum, Number::intValue));
       register(Double.class, new SumOp<>(0., Double::sum, Number::doubleValue));
       //todo: add more SumOp for other Number types
    }

    public static <T extends Number> void register(Class<T> type,
                                                   SumOp<T> sumOp) {
        OPERATORS.put(type, sumOp);
    }

    public static <T extends Number> SumOp<T> of(Class<T> type) {
        return (SumOp<T>) OPERATORS.computeIfAbsent(type, it -> {
            String message = "No SumOp registered for type:" + type.getName();
            throw new IllegalArgumentException(message);
        });
    }

    public SumOp(T identity,
                 BinaryOperator<T> plusOp,
                 Function<Number, T> valueExtractor) {
        this.identity = identity;
        this.valueExtractor = valueExtractor;
        this.plusOp = plusOp;
    }

    public <I extends Number> T sum(List<I> numbers,
                                    Predicate<I> condition) {
        return sum(numbers.stream().filter(condition));
    }

    public T sum(Stream<? extends Number> stream) {
        return stream.reduce(identity, this::plus, plusOp);
    }

    private T plus(Number augend, Number addend) {
        return plusOp.apply(valueIn(augend), valueIn(addend));
    }

    private T valueIn(Number it) {
        return valueExtractor.apply(it);
    }
}

Comments

1

A much simpler approach I tired is this.

The point to be noted is that the addition logic doesn't happen at the invoking side instead only within the MathUtility. The downside here is that you have to create Addition classes for every Number type you want the + operation.

System.out.println(
                MathUtility.sum(listOfInts, i->i<4, new MathUtility.IntegerAddition()).get()
); 

class MathUtility<T extends Number> {

    static class IntegerAddition implements BinaryOperator<Integer> {

        @Override
        public Integer apply(Integer t, Integer u) {
            return t + u;
        }

    }


    public static <T extends Number> Optional<T> sum(List<T> list, Predicate<T> condition, BinaryOperator<T> operation){
        //ability to add is only here
            return list.parallelStream()
            .filter(condition)
            .map(i -> i)
            .reduce(operation);
    }

}

4 Comments

Hi, the code above you can using Integer::sum instead of IntegerAddition. and the sum method is more like a reduce method due to it is always take a BinaryOperator.
“the addition logic” is spread over two places, the implementation within MathUtility and the invoking side which makes the decision to use new MathUtility.IntegerAddition() as operation argument to sum. When using Integer::sum instead, the decision still takes place at the invocation side, but without the need to know anything about that nested type of MathUtility.
@Holger MathUtility is a class to hold many different mathematical functions (it's just that I took Integer as an example) and yes in the case of Integers it makes sense to use Integer::sum but what I want to project here is that the actual operation is done in the utility class
You can still have a static final BinaryOperator<Integer> INTEGER_SUM = Integer::sum; (or INTEGER_SUM = (a,b) -> a+b;) in your MathUtility class… But why does MathUtility have a type parameter? And what’s the point of the .map(i -> i) step?
-2

The answer is yes, that should be possible. The you defined is not known to have the method "sum", therefore the compiler complains. Try to define

public interace SumInterface {
   public int sum(int a, int b);
}

(I haven't tried this code in IDE but this should do the trick)

2 Comments

How would this work specifically with the OP's code?
I see, he amended the question

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.