Java группирует элементы потока по несвязанному набору элементов

Теперь это проблема, я хочу сгруппировать поток элементов по другому несвязанному элементу с сопоставимыми атрибутами.

У меня есть класс видов, состоящий из диапазона температур (int lower, upper) и класс Planet, состоящий из имени и постоянной температуры (String name; int temp).

public class Species{
  int lower, upper;

  constructor....
  getter....
}

public class Planet{
  String name;
  int temp;

  contructor....
  getter....
}

Как я могу сгруппировать свой Поток планеты по видам, которые могут жить на них (когда температура планеты находится в пределах температурного диапазона вида), что приведет к:

Map <Planet, Set<Species>>

Вот, например, поток планет, которые я хочу сгруппировать:

Set<Species> speciesSet = Stream.of(new Species(5, 70), new Species(100, 220), new Species(75, 80)).collect(Collectors.toSet());

Stream.of(new Planet("blue planet", 45), new Planet("red planet", 150), new Planet("green planet", 77)).collect(Collectors.groupingBy(       , Collectors.toSet()));

Моей первой идеей о том, что мне следует сгруппировать, было что-то вроде этого - plnt -> first element from speciesSet.stream() filtered by plnt.getTemp

но это выглядит крайне неэффективно, а также будет проблематично, если на самом деле не будет видов, соответствующих температуре планеты, которые могли бы вернуться в качестве первого элемента.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
68
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Для каждой планеты вы определяете, какие виды способны жить, а затем группируете их по планете:

Stream.of(..planets...)
      .flatMap(p -> speciesSet.stream().filter(s -> s.lower <= p.temp && p.temp <= s.upper).map(s -> new SimpleEntry<>(p, s)))
      .collect(Collectors.groupingBy(Entry::getKey, mapping(Entry::getValue, Collectors.toSet())));

Если ваши планеты уже уникальны, вам даже не нужен groupingBy:

Map<Planet, Set<Species>> result = new HashMap<>();
Stream.of(..planets...)
      .forEach(p -> result.put(p, 
                          peciesSet.stream()
                              .filter(s -> s.lower <= p.temp && p.temp <= s.upper)
                              .collect(Collectors.toSet()))
Ответ принят как подходящий

Фактически, все, что вам нужно, это поиск, который может сопоставить планету с набором совместимых видов. И это довольно легко сделать, если вы используете класс "сопоставления", например следующий (и это необходимо только потому, что Java не имеет какой-либо структуры данных типа Tuple):

public static class PlanetSpecies {
    Planet planet;
    Set<Species> species;

    public PlanetSpecies(Planet planet, Set<Species> species) {
        this.planet = planet;
        this.species = species;
    }

    public Planet getPlanet() {
        return planet;
    }

    public Set<Species> getSpecies() {
        return species;
    }
}

Используя это, вы можете легко создать коллекцию PlanetSpecies из ваших двух коллекций следующим образом:

Set<PlanetSpecies> planetSpecies = Stream
        .of(new Planet("blue planet", 45), 
            new Planet("red planet", 150), 
            new Planet("green planet", 77))
        .map(speciesMapper)
        .collect(Collectors.toSet());

Это приводит к следующему результату, который по сути является вашим исходным списком планет, сопоставленным с набором его совместимых видов:

[ {
  "planet" : {
    "name" : "blue planet",
    "temp" : 45
  },
  "species" : [ {
    "lower" : 5,
    "upper" : 70
  } ]
}, {
  "planet" : {
    "name" : "green planet",
    "temp" : 77
  },
  "species" : [ {
    "lower" : 75,
    "upper" : 80
  } ]
}, {
  "planet" : {
    "name" : "red planet",
    "temp" : 150
  },
  "species" : [ {
    "lower" : 100,
    "upper" : 220
  } ]
} ]

Это в некотором смысле означает, что вам не нужно «группироваться».

Однако, если вам нужен результат, похожий на Map<planet_name, Set<Species>>, необходимо сделать еще кое-что:

Map<String, Set<Species>> planetSpecies = Stream
        .of(new Planet("blue planet", 45), 
            new Planet("red planet", 150), 
            new Planet("green planet", 77))
        .map(speciesMapper)
        .collect(
            Collectors.groupingBy(
                (PlanetSpecies sp) -> sp.getPlanet().getName(),
                Collectors.mapping(species -> species.getSpecies(), 
                        Collectors.reducing(new HashSet<Species>(), speciesReducer)
                        )
                ));

Редуктор объявлен как:

BinaryOperator<Set<Species>> speciesReducer = (set1, set2) -> {
    Set<Species> newSet = new HashSet<>();

    newSet.addAll(set1);
    newSet.addAll(set2);

    return newSet;
};

Этот редуктор в основном выполняет set.union.

Результатом выше будет следующий результат:

{
  "green planet" : [ {
    "lower" : 75,
    "upper" : 80
  } ],
  "blue planet" : [ {
    "lower" : 5,
    "upper" : 70
  } ],
  "red planet" : [ {
    "lower" : 100,
    "upper" : 220
  } ]
}

Во-первых, я бы создал этот метод, чтобы упростить код и улучшить читаемость:

class Planet {
    boolean canSupport(Species species) {
        return temp >= species.getLower() && temp <= species.getUpper();
    }
}

Это также позволяет вам позже легко добавлять дополнительные условия, например требования к атмосфере и требованиям к местным звездам.

Затем сделайте это:

import static // various classes
Map<Planet, Set<Species>> map = Stream.of(...) // Stream of Planets
    .distinct() // use this if stream has duplicate Plansts (unlikely)
    .map(p -> new SimpleEntry(p, speciesSet.stream().filter(p::canSupport).collect(toSet()))) // collect species for planet
    .filter(e -> !e.getValue().isEmpty()) // ignore planets supporting no species
    .collect(toMap(Entry::getKey, Entry::getValue));

Полученная карта будет содержать только планеты, которые могут поддерживать как минимум 1 вид, но только один раз рассчитать список видов.

—-

Чтобы повысить эффективность поиска видов, поместите их в два TreeMap<Integer, Set<Species>>, один для верхней и один для более низкой температуры, затем используйте методы NavigableMaps (оставленные читателю), чтобы найти поддерживаемые виды в O (k) (где k - количество поддерживаемых видов) вместо O (n) (количество всех видов).

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