Java:按一组不相关的元素分组流元素

3

现在问题来了,我希望能够按照具有可比属性的另一个无关元素对一系列元素进行分组。

我有一个物种类,包括温度范围(int lower, upper),还有一个行星类,包括名称和恒定温度(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 -> 从speciesSet.stream()筛选出plnt.getTemp匹配的第一个元素

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

3个回答

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
事实上,您要找的只是一个查找表,可以将行星映射到一组兼容的物种。如果使用“映射”类(仅因为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
  } ]
}

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>> 中,一个用于上限温度,一个用于下限温度,然后使用 NavigableMap 的方法(留给读者)在 O(k) 的时间复杂度内查找支持的物种(其中 k 是支持的物种数量),而不是 O(n)(所有物种的数量)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接