3

I have the following object:

public class Book {

    private Long id;
    private Long bookId;
    private String bookName;
    private String owner;
}

Represented from following table: enter image description here

Basically, a book can be owned by multiple owners i.e. Owner "a" owns books 1 and 2.

I have a basic function that will when passed a book object, will give its owner(s) in a List.

private List<String> getBookToOwner(Book book) {
        List<String> a = new ArrayList<>();
        if (book.getOwner() != null && !book.getOwner().isEmpty()) {
            a.addAll(Arrays.asList(book.getOwner().split("/")));
        }
        return a;
}

I want to use that to apply to each book, retrieve their owners and create the following Map.

Map<String, List<Long>> ownerToBookMap;

Like this:

enter image description here

How do I use streams here?

//books is List<Book>
Map<String, List<Long>> ownerToBookMap = books.stream().map( 
// apply the above function to get its owners, flatten it and finally collect it to get the above Map object
// Need some help here..
);
Eklavya
  • 17,618
  • 4
  • 28
  • 57
Mihir
  • 531
  • 2
  • 10
  • 35
  • Just a recommendation: don't include multiple values in a single column (the owner column in your case). So, to fix it I recommend that you create a many-to-many relationship with the Owner table. In that case an intermediate mapping table will be created. check online for further details. – Jalil.Jarjanazy Sep 20 '20 at 14:51

3 Answers3

3

You can get the owner list from the book, then flatten the owners and map as pair of bookId and owner using flatMap. Then grouping by owner using groupingBy and collect the list of bookId of owner.

Map<String, List<Long>> ownerToBookMap = 
     books.stream()
          .flatMap(b -> getBookToOwner(b)
                         .stream()
                         .map(o -> new AbstractMap.SimpleEntry<>(o, b.getBookId())))
          .collect(Collectors.groupingBy(Map.Entry::getKey,
                     Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
Eklavya
  • 17,618
  • 4
  • 28
  • 57
0

I use reduce instead of map.

Map<String, List<Long>> ownerToBookMap = books.stream().reduce(
    HashMap::new,
    (acc,b) -> {
        getBookToOwner(b).stream().forEach( o -> {
            if (!acc.containsKey(o))
                acc.put(o, new ArrayList<Long>());
            acc.get(o).put(b.bookId);
        });
        return acc;
    }
).get();
Chayne P. S.
  • 1,558
  • 12
  • 17
0

Flatmap the owners into a single one, create entries with key as an single owner and value as a bookId. Then group the structure by the key (owner). Finally use Collectors::mapping to get the List of bookIds instead of the actual entries:

List<Book> books = ...

Map<String, List<Long>> booksByOwner = books.stream()
    .flatMap(book -> Arrays.stream(book.getOwner().split("/"))
             .map(owner -> new AbstractMap.SimpleEntry<>(owner, book.getBookId())))
    .collect(Collectors.groupingBy(
             AbstractMap.SimpleEntry::getKey, 
             Collectors.mapping(AbstractMap.SimpleEntry::getValue, Collectors.toList())));
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • So if a book has, for example, three owners, you create three, separate `Book` objects where each `Book` has precisely one owner. Then you can perform the `groupingBy` since each `Book` has only one owner. Isn't that expensive (in terms of time and resources)? – Abra Sep 18 '20 at 08:01
  • Actually, the short-lived objects are consumed by the garbage collector almost immediatelly. You can use `AbstractMap.SimpleEntry` instead. – Nikolas Charalambidis Sep 18 '20 at 08:55