如何按Java列表中对象的属性进行分组?

时间:2018-06-06 14:55:35

标签: java java-8 java-stream

我有一个资源对象列表。资源对象定义为:

class Resource {
    String ip;
    String subnet;
    // other properties...
}

我需要一个像地图一样的数据表,其中密钥是一个子网中的随机资源,value是其他子网中所有资源的列表。

例如,如果S表示子网而R表示资源,则列表包含:

S1R1, S1R2, S2R1, S2R2, S3R1 and S3R2

预期输出为:

S1R1 -> S2R1, S2R2, S3R1, S3R2
S2R2 -> S1R1, S1R2, S3R1, S3R2
S3R1 -> S1R1, S1R2, S2R1, S2R2

我尝试使用流和收集哪些基于子网分组,不知道如何从这里开始。

Map<String, Set<Resource>> grouped = resourceList.stream()
    .collect(Collectors.groupingBy(Resource::getSubnet, Collectors.toSet()));

2 个答案:

答案 0 :(得分:2)

这不适用于groupingBy收集器,因为它通过将属于组的元素传递给每个组的下游收集器而无法告诉它传递其他组件。

实现你所描述的目标的一种方法是

TreeMap<Resource, Set<Resource>> map
                              = new TreeMap<>(Comparator.comparing(Resource::getSubnet));
for(Resource resource: resourceList)
    map.computeIfAbsent(resource, x -> new HashSet<>(resourceList)).remove(resource);

map.forEach((resource,set) -> System.out.println(resource+" -> "
    +set.stream().map(Resource::toString).collect(Collectors.joining(", "))));

其中,鉴于列表S1R1, S1R2, S2R1, S2R2, S3R1, S3R2将产生

S1R1 -> S3R2, S2R1, S2R2, S3R1
S2R1 -> S3R2, S1R2, S3R1, S1R1
S3R1 -> S1R2, S2R1, S2R2, S1R1

虽然Set元素的顺序未定义,但Map的键实际上不是随机选取的,而是每个子网中第一个遇到的资源。

您可以使用两个链式流操作来执行此操作

Map<Resource, Set<Resource>> map = resourceList.stream()
    .collect(Collectors.groupingBy(Resource::getSubnet, Collectors.toSet()))
    .values().stream()
    .collect(Collectors.toMap(
        set -> set.iterator().next(),
        set -> {
            Set<Resource> other = new HashSet<>(resourceList);
            other.removeAll(set);
            return other;
        }));

此处,从每个组中挑选的资源确实未指定。与基于TreeMap的方法不同,您必须知道实际的Resource(或尝试全部)来查找组。 TreeMap允许使用同一子网中的任何Resource来查找组。

答案 1 :(得分:0)

我不确定子网的工作原理,但你必须弄清楚下面的关系( isSubNetOf )。

BiPredicate<Resource, Resource> isSubNetOf = (parent, subnet) -> ???;

Map<Resource, Set<Resource>> resourceToSubnets = resourceList.stream()
        .collect(toMap(
                Function.identity(),
                x -> resourceList.stream().filter(v -> isSubNetOf.test(x, v)).collect(toSet())
                ));

如果您想要Map<String, Set<Resource>>更改Function.identity()所需的Resource::getIpResource::getSubnet