java组流元素由不相关的元素集组成

时间:2018-05-26 12:53:16

标签: java collections java-stream

现在这就是问题,我想用另一个具有可比属性的不相关元素对元素流进行分组。

我有一个由温度范围组成的Species类(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

但这看起来非常低效,如果实际上没有适合行星温度的物种作为第一个元素返回,那么也会有问题

3 个答案:

答案 0 :(得分:1)

对于每个行星,你会发现哪些物种能够生存,然后按行星分组:

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()))

答案 1 :(得分:1)

事实上,您正在寻找的是一个可以将行星映射到兼容物种集合的查找。如果你使用如下所示的“映射”类,这很容易实现(这只是因为Java没有任何类型感知,类似于元组的数据结构):

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)
                        )
                ));

将reducer声明为:

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

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

    return newSet;
};

这个reducer基本上是set.union

以上结果如下:

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

答案 2 :(得分:1)

首先,我将创建此方法以简化代码并提高可读性:

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>> s,一个用于上层,一个用于低温,然后使用NavigableMap方法(留给读者)在O中找到可支持物种(k)(其中k是可支持物种的数量)而不是O(n)(所有物种的数量)。