现在这就是问题,我想用另一个具有可比属性的不相关元素对元素流进行分组。
我有一个由温度范围组成的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
但这看起来非常低效,如果实际上没有适合行星温度的物种作为第一个元素返回,那么也会有问题
答案 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)(所有物种的数量)。