jhenya-d
jhenya-d

Reputation: 399

Java stream, get map of lists of objects

Trying to get a map of lists using java 8. I have array of predifined titles:

String[] myStringArray = {"TITLE1", "TITLE2", "TITLE3"};

And a list of some Pages objects (List<String>).

I need to create a map of lists of objects with keys from the predefined array if the value from this array was found on the page. The result should be like:

{TITLE1=[page1, page2, page3], TITLE2=[page3, page4, page5]} 

(let's assume that TITLE3 was not found in Page list)

My code:

Arrays.stream(myStringArray)
        .map(title -> page
                .stream()
                .filter(p -> isPageContainsTitle(p, title))
                .collect(Collectors.toList()))
        .filter(Objects::nonNull)
        .collect(Collectors.toMap(e->e, Function.identity()));

As a result i get Map of lists with list in key instead of value from myStringArray:

{[page1, page2, page3]=[page1, page2, page3], [page3, page4, page5]=[page3, page4, page5]} 

Upvotes: 2

Views: 8896

Answers (4)

Ryuzaki L
Ryuzaki L

Reputation: 39978

Just make sure array has unique titles and then use Collectors.toMap

Map<String, List<String>> result = Arrays.stream(myStringArray)
            .map(title -> new AbstractMap.SimpleEntry<>(title,
                    pages.stream().filter(page -> isPageContainsTitle(page,title)).collect(Collectors.toList())))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

And as @Naman suggested you can avoid the intermediate map operation

Map<String, List<String>> result = Arrays.stream(myStringArray) 
                     .collect(Collectors.toMap(Function.identity() 
                                        ,title -> pages.stream()
                                                       .filter(page -> isPageContainsTitle(page, title))
                                                       .collect(Collectors.toList())));

Upvotes: 3

Shrirang Kumbhar
Shrirang Kumbhar

Reputation: 363

Below code should work for you:

package com.shree.test;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamTest {
    public static void main(String[] args) {
        String[] myStringArray = {"TITLE1", "TITLE2", "TITLE3"};
        List<String> pages = new ArrayList<>();
        pages.add("TEXT,TITLE2,TEXT,ONE,TWO");
        pages.add("TEXT,TITLE1,TEXT,ONE,TWO");
        pages.add("TEXT,TITLE2,TEXT,ONE,TWO");
        pages.add("TEXT,TITLE2,TEXT,TITLE3,TWO");
        pages.add("TEXT,TITLE3,TEXT,ONE,TWO");



         Map<Object, Object> titleMap = getTitleMap(myStringArray, pages);

         titleMap.entrySet().forEach(System.out::println);
    }

    private static Map<Object, Object> getTitleMap(String[] myStringArray, List<String> pages) {
        Map<Object, Object> titleMap = Arrays.asList(myStringArray).stream()
            .map(
                    (title)->{
                        return new AbstractMap.SimpleEntry<>(title, pages.stream()
                                .filter((page)->page.contains(title))
                                .collect(Collectors.toList()));
                    }
                )
            .collect(Collectors.toMap(entry->entry.getKey(), entry->entry.getValue()));
        return titleMap;
    }
}

Upvotes: 2

WJS
WJS

Reputation: 40024

The only way I can see to do this with the separate objects it to map them to a two element array of types String and List<Page> to an array and then use those entries to build the map.

The Object[]{} array will contain the title and the list. This was the only way to preserve the two values across nested streams.

I later cast them to create a Map<String,List<Page>>

This requires no modification of your existing data structures.

 Map<String, List<Page>> map = Arrays.stream(myStringArray)
            .map((String title) -> new Object[] {title,
                                       page.stream().filter(
                                   (Page p) -> isPageContainsTitle(p, title)).collect(
                                    Collectors.toList())})
            .filter(arr->arr[1] != null)
            .collect(Collectors.toMap(arr -> (String)arr[0], arr -> (List<Page>)arr[1]));

There is a type safety warning in the final List cast but it should be okay.

Upvotes: 0

tpdi
tpdi

Reputation: 35141

So

 e->e

and

  Function.identity()

are and should be equivalent.

What you really want is a flatMap:

  Arrays.stream(myStringArray)
                .flatMap(title -> pages
                        .stream()
                        .filter(p -> isPageContainsTitle(p, title))
                        .map( p -> new Tuple(t, p)) 
                 )
                 .collect(Collectors.groupingBy( t -> t._1, t -> t._2));

We want flatMap because we may get back 0, 1 or many Tuples of (title, page)

This assumes you have a Tuple class, I'm modelling one based on Scala's Tuple2.

Note, I have not tested this code.

Upvotes: 0

Related Questions