Группировка Java Stream Где ключ - это элемент в подсписке

У меня есть класс Food:

public class Food {
    private Collection<FoodToFoodGroup> foodToFoodGroups;
    //getters and setters
}

А вот FoodToFoodGroup выглядит так:

public class FoodToFoodGroup {
    private Food food;
    private FoodGroup foodGroup;
    //getters and setters
}

Наконец, у каждого FoodGroup есть имя:

public class FoodGroup {
    private String name;
    //getters and setters
}

Все эти классы являются объектами, отображаемыми через спящий режим. Я получаю коллекцию всех сущностей Food в базе данных с помощью вызова foodRepository.findAll(), и я пытаюсь сгруппировать все экземпляры Food по имени их FoodGroup, чтобы в итоге получить Map<String, Collection<Food>>. Я пытался сделать это через Stream API, но не могу придумать способ сделать это с помощью сборщика Collectors.groupingBy.

Map<String, Collection<Food>> foodsByFoodGroupName = 
    foodRepository.findAll().stream()
        .collect(Collectors.groupingBy(
            //what goes here?
        ));

Обратите внимание, что Food может быть в нескольких FoodGroup, поэтому мне понадобятся любые Food, которые находятся в нескольких FoodGroup, чтобы они отображались в нескольких Collection в результирующем Map. Возможно ли это с помощью Stream API и Collectors.groupingBy()?

Пример

Если у меня есть следующие продукты:

Food: Pimento Cheese
    - FoodToFoodGroups: [
        - FoodGroup: cheese,
        - FoodGroup: dairy
      ]
Food: Chocolate Milk
    - FoodToFoodGroups: [
        - FoodGroup: milk,
        - FoodGroup: dairy
      ]

Я бы хотел, чтобы получившийся Map выглядел так:

Key: cheese
    - Collection<Food>: [Pimento Cheese]
Key: milk
    - Collection<Food>: [Chocolate Milk]
Key: dairy
    - Collection<Food>: [Pimento Cheese, Chocolate Milk]

Возможно, я неправильно понимаю, но когда у Food есть коллекция FoodToFoodGroup, кажется, что у каждого Food есть несколько FoodGroup, так как же можно "сгруппировать все экземпляры Food по имени их FoodGroup"? Какой FoodGroup в коллекции экземпляра Food будет считаться их?

Max Vollmer 04.06.2018 17:18

С гибернацией должен быть более простой способ поддерживать связь между Food и FoodGroup. Похоже на @ManyToMany

John 04.06.2018 17:22

@MaxVollmer Я добавил пример, чтобы помочь понять

Andrew Mairose 04.06.2018 17:26

Я думаю, что в вашей модели данных есть ненужные циклические ссылки. Каждый Food может просто содержать коллекцию FoodGroups; объект FoodToFoodGroup кажется ненужным. Похоже, вы пытаетесь представить нормализованную таблицу реляционной базы данных непосредственно как объект, вместо того, чтобы объединять данные в структурированном виде. Хотя это на самом деле не влияет на решение проблемы, я думаю, что это делает семантически более трудным обсуждение.

cosmicFluke 04.06.2018 17:40

Но проблема здесь в том, что у экземпляра Food может быть несколько экземпляров FoodGroup. Итак, как вы собираетесь сгруппировать такую ​​штуку?

Ravindra Ranwala 04.06.2018 17:44

Ваш пример, похоже, не соответствует классам, которые вы дали. Читая ваш пример, я ожидал, что будет класс Food с набором FoodGroup, а не с набором FoodToFoodGroup.

Max Vollmer 04.06.2018 18:03

@MaxVollmer Я обновил пример, чтобы точнее отразить структуру класса

Andrew Mairose 04.06.2018 18:41

Теперь в вашем примере FoodToFoodGroup содержат ТолькоFoodGroup, но в вашем коде они содержат Food и FoodGroup. Из контекста я могу предположить, что каждый FoodToFoodGroup в коллекции Food указывает на тот же Food (в основном круговая ссылка), но это не ясно в вашем коде. Там ничего не ограничивает что-то вроде FoodA -> FoodToFoodGroup -> FoodB. Также все еще не ясно, Зачем у вас есть коллекция FoodToFoodGroup, когда Food всегда является родительским. Достаточно коллекции FoodGroup.

Max Vollmer 04.06.2018 18:48
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
8
437
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

это то, что ты ищешь?

foodRepository.findAll().stream()
     .map(Food::getFoodToFoodGroup)
     .flatMap(Collection::stream)
     .collect(Collectors.groupingBy(
         f -> f.getFoodGroup().getName(),
         HashMap::new,
         Collectors.mapping(FoodToFoodGroup::getFood, Collectors.toList())));

Идеально! Collectors.mapping - именно то, что я искал.

Andrew Mairose 04.06.2018 17:50

Я не думаю, что groupingBy - это то, что вам здесь нужно, поскольку он не предназначен для размещения одного и того же элемента в нескольких группах.

Для не чисто функционального решения с использованием потоков вы можете плоско сопоставить каждый Food с набором записей foodGroupToFood, а затем добавить все пары на карту с помощью BiConsumer.

BiConsumer<Map<FoodGroup, Set<Food>>, Map.Entry<FoodGroup, Food>> addEntryToMapOfSets = (map, entry) -> {
    if (map.containsKey(entry.key()))
        map.get(entry.key()).add(entry.value());
    else
        foodGroupToFoods.put(entry.key(), Stream.of(entry.value()).collect(Collectors.toSet()))
};
Map<String, Collection<Food>> foodGroupToFoods = new HashMap<>();
foods.stream()
  .flatMap(food -> food.getFoodToFoodGroup().stream()
       .collect(Collectors.toMap(pair -> pair.foodGroup, pair -> pair.food))
       .entrySet())
  .forEach(entry -> addEntryToMapofSets(foodGroupToFoods, entry));

Отказ от ответственности: я давно не писал на Java, пожалуйста, исправьте мое использование Streams API, если это требует исправления. Обновлено: это, вероятно, сложнее, чем должно быть.

Другие вопросы по теме