【问题标题】:How to group by a property of an object in a Java List?如何按 Java List 中对象的属性进行分组?
【发布时间】:2018-11-16 07:41:24
【问题描述】:

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

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

我需要一个像地图这样的数据结构,其中键是一个子网中的随机资源,值是其他子网中所有资源的列表。

例如,如果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()));

【问题讨论】:

  • 请详细说明预期结果。
  • 预期结果是应该有一个映射或类似结构,其中键是一个子网中的任意随机资源,值是所有其他子网中所有资源的列表。

标签: java java-8 java-stream


【解决方案1】:

我不确定子网是如何工作的,但你必须弄清楚下面的关系(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&lt;String, Set&lt;Resource&gt;&gt;,请将Function.identity() 更改为所需的Resource::getIpResource::getSubnet

【讨论】:

    【解决方案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 查找组。

    【讨论】:

    • 不会String.join(", ", set) 导致编译错误或者我在这里遗漏了什么?
    • 次要,但Set&lt;String&gt; other = new HashSet&lt;&gt;(list); 中的list 未在任何地方声明。
    • 对不起,如果我在这里很笨,但是 list 应该声明为什么?一个空列表?此外,other.removeAll(set) 正在引发类型不兼容的错误:Collection cannot be applied to object
    • 知道了。稍微修改了答案:` Collection> values = resourceList.stream().collect(Collectors.groupingBy(Resource::getSubnet, Collectors.toSet())).values(); Map> set = values.stream()。 collect(Collectors.toMap(set -> set.iterator().next(), set -> { HashSet other = new HashSet(resourceList); other.removeAll(set); return other; } )) ; `
    • @Aominè 我的第一次测试有一些剩余,我只使用字符串,所以list 应该是resourceListString 应该是Resource,是的,我们当集合的元素类型为Resource 时,不能简单地使用String.join。我修好了它;打印代码仍然假定Resource.toString() 返回subnet+ip,但这不应该影响程序逻辑。
    猜你喜欢
    • 1970-01-01
    • 2021-11-25
    • 2020-08-03
    • 2015-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多