繁体   English   中英

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM