6

Scenario: I have a object with 2 functions -->

<Integer> getA(); <Integer> getB();

I have a list of objects, say List<MyObject> myObject.

Objective Iterate over the List and get separate sums of A and B's in the List of object.

My Solution

myObject.stream().map(a -> a.getA()).collect(Collectors.summingDouble());

myObject.stream().map(a -> a.getB()).collect(Collectors.summingDouble());

The Question: How can I do both of these at the same time? This way I will not have to iterate twice over the original list.

@Edit: I was doing this because some of the filters that I used were of O(n^3). Kind of really bad to do those twice.

Benchmark : It really matters if it is T or 2T when the program runs for half hour on an i5. This was on much lesser data and if I run on a cluster, my data would be larger too.

It does matter if you can do these in one line!.

0

3 Answers 3

3

You need to write another class to store the total values like this:

public class Total {
    private int totalA = 0;
    private int totalB = 0;

    public void addA(int a) {
        totalA += a;
    }

    public void addB(int b) {
        totalB += b;
    }

    public int getTotalA() {
        return totalA;
    }

    public int getTotalB() {
        return totalB;
    }
}

And then collect the values using

Total total = objects.stream()
        .map(o -> (A) o)
        .collect(Total::new,
                (t, a) -> {
                    t.addA(a.getA());
                    t.addB(a.getB());
                },
                (t1, t2) -> { });
//check total.getTotalA() and total.getTotalB()

You can also use AbstractMap.SimpleEntry<Integer, Integer> to replace Total to avoid writing a new class, but it's still kind of weird because A/B are not in a key-value relationship.

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

5 Comments

So essentially you created another Collector? I could just extend my existing class MyObject to collect this by exposing addA/B() to it. Seems like a good way.
@user6184793 Of course you can use an existing class to replace my Total class.
+1. Note that it's a good idea to benchmark afterward to see if this approach actually performs better than iterating once for A and once for B.
@ruakh, This example is simple. Suppose I had a few filters in place. Something like .stream()..filter(myObj.x())....filter(myObj.y()).collect....). That would definitely mean doing things twice.
@user6184793: And even so, it's worth benchmarking to see how much of a difference it really makes. You don't want to complicate your code for no benefit.
3

If you're running Java >= 12, this isn't hard at all to do in a way that's even compatible with parallel streams (unlike the other answers so far), using Collectors.teeing() and Collectors.summingInt():

int[] totals = myObject.stream()
   .collect(Collectors.teeing(
      Collectors.summingInt(MyObject::getA),
      Collectors.summingInt(MyObject::getB),
      (a, b) -> new int[] {a, b});

int sumA = totals[0];
int sumB = totals[1];

Comments

0

The most optimal probably still would be a for loop. Though the stream alternative has parallelism as option, and is likely to become as efficient soon. Combining two loops to one is not necessarily faster.

One improvement would be to use int instead of Integer.

List<String> list = Arrays.asList("tom","terry","john","kevin","steve");
int n = list.stream().collect(Collectors.summingInt(String::length));
int h = list.stream().collect(Collectors.summingInt(String::hashCode));

I favour this solution.

If one would make one loop, there are two alternatives:

  • putting both ints in their own class. You might abuse java.awt.Point class with int x and int y.
  • putting both ints in a long. When no overflow, then one can even sum in the loop on the long.

The latter:

List<String> list = Arrays.asList("tom","terry","john","kevin","steve");
long nh = list.stream()
    .collect(Collectors.summingLong((s) -> (s.hashCode() << 32) | s.length()));
    int n = (int) nh;
    int h = (int) (nh >> 32L);

1 Comment

Combining two loops is not significantly faster, but if you are doing things like filter(), it will be definitely bad because of doing all the checks twice!

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.