How to group objects from a list which can belong to two or more groups?

拥有回忆 提交于 2021-02-01 03:59:08

问题


I have a list of Items where each Item can belong to one or more category. For a limited set of categories(string) I want to create a map with category as key and list of Items as value.

Assume my Item class is defined as shown below:

public static class Item{
    long id;
    List<String> belongsToCategories;

    public List<String> getBelongsToCategories() {
        return belongsToCategories;
    }

    public void setBelongsToCategories(List<String> belongsToCategories) {
        this.belongsToCategories = belongsToCategories;
    }

    public Item(long id,List<String> belongsToCategories) {
        this.id = id;
        this.belongsToCategories = belongsToCategories;
    } 

    @Override
    public String toString() {
        return "Item{" + "id=" + id + '}';
    }        
}

and a list of Items:

public static void main(String[] args) {
    List<Item> myItemList   = new ArrayList<>();

    myItemList.add(new Item(1,Arrays.asList("A","B")));
    myItemList.add(new Item(2,Arrays.asList("A","C")));
    myItemList.add(new Item(3,Arrays.asList("B","C")));
    myItemList.add(new Item(4,Arrays.asList("D")));
    myItemList.add(new Item(5,Arrays.asList("D","E")));
    myItemList.add(new Item(6,Arrays.asList("A","F")));

    Map<String,List<Item>> myMap= new HashMap<>();

How can i fill myMap from myList?

I thought Stream API could help, but I don't know which classifier to put in the groupingBy method when an Item can belong to one or more categories

 myItemList.stream().collect(Collectors.groupingBy(classifier));

This

myItemList.stream().collect(Collectors.groupingBy(Item::getBelongsToCategories));

produces

[D, E]=[Item{id=5}]
[B, C]=[Item{id=3}]
[A, B]=[Item{id=1}]
[D]=[Item{id=4}]
[A, C]=[Item{id=2}]
[A, F]=[Item{id=6}]

expected is tough something like:

A=[Item{id=1}, Item{id=2}, Item{id=6}]
B=[Item{id=1}, Item{id=3}]
C=[Item{id=2}, Item{id=3}]
D=[Item{id=4}, Item{id=5}]
E=[Item{id=5}]
F=[Item{id=6}]

回答1:


You can use flatMap to map to SimpleEntry and then groupingBy as :

return items.stream()
        .flatMap(p -> p.getBelongsToCategories()
                .stream()
                .map(l -> new AbstractMap.SimpleEntry<>(l, p)))
        .collect(Collectors.groupingBy(Map.Entry::getKey,
                Collectors.mapping(Map.Entry::getValue,
                        Collectors.toList())));



回答2:


You can use groupByEach from Eclipse Collections.

Multimap<String, Item> itemsByCategory =
        ListIterate.groupByEach(myItemList, Item::getBelongsToCategories);

System.out.println(itemsByCategory);

Output:

{D=[Item{id=4}, Item{id=5}], 
E=[Item{id=5}], 
F=[Item{id=6}], 
A=[Item{id=1}, Item{id=2}, Item{id=6}], 
B=[Item{id=1}, Item{id=3}], 
C=[Item{id=2}, Item{id=3}]}

You can also use the Collectors2 utility class and groupByEach Collector with a Java Stream.

Multimap<String, Item> itemsByCategory = myItemList.stream().collect(
        Collectors2.groupByEach(
                Item::getBelongsToCategories,
                Multimaps.mutable.list::empty));

Note: I am a committer for Eclipse Collections.




回答3:


Here is the solution by ListMultimap, either from Google Guava or my library Abacus-Util

ListMultimap<String, Item> result = Multimaps.newListMultimap(new HashMap<>(), () -> new ArrayList<>()); // by Google Guava.
// Or result = N.newListMultimap(); // By Abacus-Util
items.forEach(item -> item.getBelongsToCategories().forEach(c -> result.put(c, item)));

I think it will be a little more efficient and concise without creating the unnecessary temporary Entry Objects by flatMap.



来源:https://stackoverflow.com/questions/55376332/how-to-group-objects-from-a-list-which-can-belong-to-two-or-more-groups

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!