简体   繁体   中英

How to group by a property of an object in a Java List?

I have a list of resource objects. Resource object is defined as:

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

I need a datastrucutre like a map where the key is a random resource in one subnet and value is a list of all resources in other subnets.

For example, if S denotes subnet and R denotes resource, the list has:

S1R1, S1R2, S2R1, S2R2, S3R1 and S3R2

Expected output is:

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

I tried using stream and collect which grouped based on subnets, not sure how to proceed from here.

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

This doesn't work with the groupingBy collector, as it works by passing the elements belonging to a group to each group's downstream collector and you can't tell it to pass the others.

One way to achieve what you've described, is

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(", "))));

Which, given the list S1R1, S1R2, S2R1, S2R2, S3R1, S3R2 will produce

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

while the order of the Set 's elements is undefined, the Map 's keys actually are not picked randomly, it's the first encountered resource of each subnet.

You could do this with two chained stream operations

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;
        }));

Here, the resource picked from each group is indeed unspecified. Unlike the TreeMap based approach, you would have to know the actual Resource (or try them all) to lookup a group. The TreeMap allows to lookup a group by using any Resource from the same subnet.

I am not sure what how subnets work but you have to figure out the below relationship( 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())
                ));

If you want a Map<String, Set<Resource>> change Function.identity() to the required Resource::getIp or Resource::getSubnet .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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