[英]Perform multiple unrelated operations on elements of a single stream in Java
[英]java group stream elements by unrelated set of elements
现在这就是问题,我想用另外一个具有可比属性的不相关元素对元素流进行分组。
我有一个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
但这看起来非常低效,如果实际上没有适合行星温度的物种作为第一元素返回,那也会有问题
对于每个行星,你会发现哪些物种能够生存,然后按照你的星球将它们分组:
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<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
} ]
}
首先,我将创建此方法以简化代码并提高可读性:
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>>
,一个用于上层,一个用于低温,然后使用NavigableMap
的方法(留给读者)在O(k)中找到可支持的物种(其中k是可支持物种的数量)而不是O(n)(所有物种的数量)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.