2

Here is my List fooList

class Foo {
    private String name;
    private int code;
    private int account;
    private int time;
    private String others;

    ... constructor, getters & setters
}

e.g.(all the value of account has been set to 1)

new Foo(First, 200, 1, 400, other1), 
new Foo(First, 200, 1, 300, other1),
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 1, 20, other2),
new Foo(Second, 400, 1, 40, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

I want to transform these values by grouping "name" and "code", accounting for the number, and summing the "time", e.g.

new Foo(First, 200, 2, 700, other1), 
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 60, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

I know that I should use a stream like this:

Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getName()));

but how can I group them by code then do the accounting and summing job?


Also, what if I want to calculate the average time? e.g.

new Foo(First, 200, 2, 350, other1), 
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 30, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)

Can I use both of summingInt(Foo::getAccount) and averagingInt(Foo::getTime) instead?

2
  • Why your name is String when you pass Integer values? Commented Jun 9, 2020 at 15:43
  • maybe I should rename it in a better way@MrFisherman Commented Jun 9, 2020 at 15:46

2 Answers 2

4

A workaround could be to deal with grouping with key as List and casting while mapping back to object type.

List<Foo> result = fooList.stream()
        .collect(Collectors.groupingBy(foo ->
                        Arrays.asList(foo.getName(), foo.getCode(), foo.getAccount()),
                Collectors.summingInt(Foo::getTime)))
        .entrySet().stream()
        .map(entry -> new Foo((String) entry.getKey().get(0),
                (Integer) entry.getKey().get(1),
                entry.getValue(),
                (Integer) entry.getKey().get(2)))
        .collect(Collectors.toList());

Cleaner way would be to expose APIs for merge function and performing a toMap.


Edit: The simplification with toMap would look like the following

List<Foo> result = new ArrayList<>(fooList.stream()
        .collect(Collectors.toMap(foo -> Arrays.asList(foo.getName(), foo.getCode()),
                Function.identity(), Foo::aggregateTime))
        .values());

where the aggregateTime is a static method within Foo such as this :

static Foo aggregateTime(Foo initial, Foo incoming) {
    return new Foo(incoming.getName(), incoming.getCode(),
            incoming.getAccount(), initial.getTime() + incoming.getTime());
}
Sign up to request clarification or add additional context in comments.

7 Comments

It seems that I can't do return new Foo(incoming.getName(),incoming.getCode(),incoming.getAccount(), initial.getTime() + incoming.getTime()); because of required: no arguments
@San.s that just needs an appropriate constructor.
I'm totally beginner in Java, would you mind teaching me how to do that? Thank you. Also it's kind of confused to me about calculating the average time.@Naman
@San.s What I did there was to iterate through the list and find out elements that are the same based on few attributes. Next, I have defined a merge function aggregateTime which takes two such similar objects and results into a merged final object (based on whatever intended logic). If your expectation is to average the time as well, then you would have to for once iterate the complete list and then evaluated the average of values found matching. But, that wouldn't be the same as summing which doesn't need an awareness of the number of elements traversed.
I see..So It's better to merge an object which has got the number of groupednameandcode , and summed time, then do a single division to get the average time. Am I right? Also I wanna know what is an appropriate constructor looks like. Thanks a lot! @Naman
|
0

You can implement your own collect mechanish that might look like following

        var collect = Stream.of(
                new Foo(1, 200, 1, 400),
                new Foo(1, 200, 1, 300),
                new Foo(1, 201, 1, 10),
                new Foo(2, 400, 1, 20),
                new Foo(2, 400, 1, 40),
                new Foo(3, 100, 1, 200),
                new Foo(3, 101, 1, 900)
        )
                .collect(
                        ArrayList::new,
                        (BiConsumer<ArrayList<Foo>, Foo>) (foos, foo) -> {
                            var newFoo = foos
                                    .stream()
                                    .filter(f -> f.name == foo.name && f.account == foo.account)
                                    .findFirst()
                                    .map(f -> new Foo(f.name, f.code, f.account + foo.account, f.time + foo.time))
                                    .orElse(foo);
                            foos.removeIf(f -> f.name == foo.name && f.account == foo.account);
                            foos.add(newFoo);
                        },
                        (foos, foos2) -> foos.addAll(foos2)
                );

2 Comments

I declared name as int. Anyway that's just a draft
I followed your approach but I couldn't fix my issue. Here is the link : stackoverflow.com/questions/74511911/…

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.