Search code examples
javajava-streamgroupingby

Java Streams grouping by on two fields


I'm trying to group a data in a list and trying to add a count back to one of the fields of each object of list. Let me put some dummy example here as follows -

public class Book{
    String title;
    String author;
    int count;
}

and there's a list as follows

List<Book> posts = Arrays.asList(
                Book.builder().title("Book_1").author("ABC").build(),
                Book.builder().title("Book_2").author("PQR").build(),
                Book.builder().title("Book_1").author("ABC").build(),
                Book.builder().title("Book_3").author("XYZ").build()
        );

Now, my objective is to group data in a list by grouping title and author and update the count in count field of Book in list. i.e. in above list as title and author have same values, the count=2 for title="Book_1" and author="ABC", and for other entries count = 1.

I used Apache commons Pair.of() in grouping by as follows -

Map<Pair<String, String>, Long> map = posts.stream()
                .collect(Collectors.groupingBy(socialMediaPost -> Pair.of(socialMediaPost.getTitle(), socialMediaPost.getAuthor()), Collectors.counting()));

This actually produces the result but the return type is not what I want. I want List<Book> back with single entry for duplicate data(title, author) with increased count and other non-duplicate data with count = 1


Solution

  • Maybe you could just use a second stream after your first? something like this where you just extract the books out of the intermediate Pair representation.

    Edit: finally, you would just increment everytime you have to combine one, starting with an initial value of 1.

    building off your idea, i think something like this would achieve the goal:

    List<Book> posts = Arrays.asList(
            Book.builder().title("Book_1").author("ABC").build(),
            Book.builder().title("Book_2").author("PQR").build(),
            Book.builder().title("Book_1").author("ABC").build(),
            Book.builder().title("Book_3").author("XYZ").build()
    );
    
    
    List<Book> books = posts.stream()
            .collect(Collectors.groupingBy(
                    socialMediaPost -> {
                        // initial value of 1
                        socialMediaPost.setCount(1);
                        return Pair.of(socialMediaPost.getTitle(),
                                socialMediaPost.getAuthor());
                    },
                    // gets the first of each repeat, incrementing count
                    Collectors.reducing((a, b) -> a.setCount(a.getCount() + 1))))
            .values()
            .stream()
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList());
    
    books.stream().map(Book::toString).forEach(System.out::println);
    
    /*
    SO_75775541.Book(title=Book_3, author=XYZ, count=1)
    SO_75775541.Book(title=Book_1, author=ABC, count=2)
    SO_75775541.Book(title=Book_2, author=PQR, count=1)
     */