Home > Back-end >  Group a list of objects by multiple enum values in a list
Group a list of objects by multiple enum values in a list

Time:10-13

Context:

I have a Topic enum:

public enum Topic {
    RELATIONSHIP, DATING, EDUCATION, FITNESS, NEWS, RENT, EVENTS, GIVEAWAY, SALE
}

and a class Member with a topics list as field. A member has at least one and up to 9 topics, to which he/she has subscribed.

@AllArgsConstructor
@Getter
@ToString
static class Member {
    String id;
    List<Topic> topics;
    String email;
}

Once a week I get a list of Messages. Each messages belongs to exactly one Topic type. The list of Messages doesn't contain Messages, which have the same topic (no duplicate topics and never empty, 1 to 9 entries)

@AllArgsConstructor
@Getter
@ToString
static class Message {
    String id;
    Topic topic;
    String content;
}

Question:

Given a list of messages and a list of members create a map Map<Message, List<Member>> to be used to send each member a notification. Each member should only receive notifications for topics, to which he/she has subscribed.

My approach:

  1. Create a Map<Topic, Message> from messages list (done as you can see in the example below)
  2. Create a Map<Topic, List<Member>> from members list. (I am stuck here, don't know how to extract single Topics from members topic list to create my desired map)
  3. after I get the above two maps create a final map <Message, List<Member>> mapping the values of first map to corresponding values of second map. Should be easy.

Example setup:

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

public class Example {
    public static void main(String[] args) {
        List<Message> messages = List.of(new Message("KW41",Topic.EDUCATION,"Some educational content published"),
                                         new Message("KW41",Topic.FITNESS,"Some fitness content published"),
                                         new Message("KW41",Topic.DATING,"Some dating content published"),
                                         new Message("KW41",Topic.RENT,"Some rent content published"));

        List<Member> memberList = List.of(new Member("1", List.of(Topic.SALE, Topic.RENT), "[email protected]"),
                                          new Member("2", List.of(Topic.DATING, Topic.NEWS), "[email protected]"),
                                          new Member("3", List.of(Topic.EDUCATION), "[email protected]"),
                                          new Member("4", List.of(Topic.FITNESS, Topic.RENT, Topic.EDUCATION), "[email protected]"),
                                          new Member("5", List.of(Topic.RELATIONSHIP), "[email protected]"),
                                          new Member("6", List.of(Topic.SALE, Topic.NEWS), "[email protected]"),
                                          new Member("7", List.of(Topic.NEWS, Topic.EVENTS), "[email protected]"));

        Map<Topic,Message> messageMap = messages.stream().collect(Collectors.toMap(Message::getTopic, Function.identity()));
        messageMap.entrySet().forEach(System.out::println);
    }

    @AllArgsConstructor
    @Getter
    @ToString
    static class Member {
        String id;
        List<Topic> topics;
        String email;
    }

    @AllArgsConstructor
    @Getter
    @ToString
    static class Message {
        String id;
        Topic topic;
        String content;
    }

    enum Topic {
        RELATIONSHIP, DATING, EDUCATION, FITNESS, NEWS, RENT, EVENTS, GIVEAWAY, SALE
    }
}

How can i get from the members list a Map<Topic, List<Member>> grouped by topics. (I cant't easily do stream.collect(Collectors.groupingBy(Member::getTopics)) since it is a list. I need to extract them somehow before). Desired map should contain (adding only members ids for readability)

Topic.RELATIONSHIP=[5] 
Topic.DATING=[2]  
Topic.EDUCATION=[3, 4] 
Topic.FITNESS=[4]  
Topic.NEWS=[2, 6, 7]  
Topic.RENT=[1, 4]  
Topic.EVENTS=[7]   
Topic.SALE=[1, 6] 

CodePudding user response:

Flattening into tuples could help.

For example with:

@Value
class Tuple<L, R> {
    L left;
    R right;
}

You could have:

Stream<Member> stream = ...;
Map<Topic, List<Member>> result = stream
        .flatMap(member -> member.getTopics().stream().map(topic -> new Tuple<>(topic, member)))
        .collect(groupingBy(Tuple::getLeft, mapping(Tuple::getRight, toList())));
  • Related