Hans Meier
Hans Meier

Reputation: 33

Java: How to add objects from List<T> to Map (Key: Enum, value List<T>)

I'm trying to add objects from the List<Farm> to Map<Animal, List<Farm>>

public class Farm {
    private String farmName;
    private EnumSet<Animal> animals = EnumSet.noneOf(Animal.class);
    /* ... */
}

Farm f1 = new Farm("Farm A", EnumSet.of(Animal.CAT, Animal.DOG, Animal.DUCK));
Farm f2 = new Farm("Farm B", EnumSet.of(Animal.PIG, Animal.CAT, Animal.HORSE));
Farm f3 = new Farm("Farm C", EnumSet.of(Animal.DUCK));

Task 1: add Objects to List<Farm>

List<Farm> list = new ArrayList<>();
list.add(f1);
list.add(f2);
list.add(f3);

Task 2: Add the objects from list to a map (Key: Animal, Value: List <Farm>) I did this task in this way:

Map<Animal, List<Farm>> map = new HashMap<>();

for(Farm farm: list) {
    for(Animal an: farm.getAnimals()) {
        if(!map.containsKey(an)) {
            List<Farm> new_list = new ArrayList<>();
            new_list.add(farm);
            map.put(an, new_list);
        }else {     
            List<Farm> old_list = map.get(an);
            if(!old_list.contains(farm)) {
                old_list.add(farm);
                    }
            }
        }
    }

Is there a second / more efficient solution? Something like this:

Map<Animal, List<Farm>> map = list.stream().collect(Collectors.groupingBy(Farm::getAnimals)));

This does not work because getAnimals returns an EnumSet<Animal>.

Upvotes: 2

Views: 265

Answers (2)

Michel_T.
Michel_T.

Reputation: 2821

I started from the opposite side, but I suppose it's helpful

    Map<Animal, List<Farm>> map = Arrays.stream(Animal.values())
            .collect(Collectors.toMap(an -> an, an -> list.stream().filter(f -> f.getAnimals().contains(an)).collect(Collectors.toList())));

There could be empty sets for an animal in the case when none of the farms contains it, but that could be easily filtered

Upvotes: 0

Holger
Holger

Reputation: 298123

You probably want to stay with the loop, but modernize it:

Map<Animal, List<Farm>> map = new EnumMap<>(Animal.class);
for(Farm farm: list)
    for(Animal an: farm.getAnimals())
        map.computeIfAbsent(an, x -> new ArrayList<>()).add(farm);

In your loop, add(farm) redundantly appeared in both branches, as you always add it to the List. Then, computeIfAbsent allows to eliminate the conditional, as it will return an existing value or construct a new value, put and return it. The groupingBy collector also uses this method internally.

Using a Stream operation for the same has the disadvantage that you need a temporary holder for two values, e.g.

Map<Animal, List<Farm>> map = list.stream()
    .flatMap(farm -> farm.getAnimals().stream()
        .map(animal -> new AbstractMap.SimpleImmutableEntry<>(animal, farm)))
    .collect(Collectors.groupingBy(Map.Entry::getKey,
        () -> new EnumMap<>(Animal.class),
        Collectors.mapping(Map.Entry::getValue, Collectors.toList())));

Upvotes: 3

Related Questions