【问题标题】:Map<K,V> back to Map<V,Map<K,V>> after groupingBy value, instead of Map<Obj, List<Entry<K,V>>>Map<K,V> 在 groupingBy 值之后返回 Map<V,Map<K,V>>,而不是 Map<Obj, List<Entry<K,V>>>
【发布时间】:2019-10-10 19:03:38
【问题描述】:

我正在努力维护我想要的跨 Java 流式操作的数据结构,这很可能是由于缺乏正确的理解和实践。

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3);

            //Group by
            Map <Integer, Long> countGrouped = list.stream().collect(
                    Collectors.groupingBy(
                            x -> x, Collectors.counting()));
            System.out.println("group by value, count " + countGrouped);

            //Sort desc
            Map <Integer, Long> descendingSorted = new LinkedHashMap<>();
            countGrouped.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .forEachOrdered(x -> descendingSorted.put(x.getKey(), x.getValue()));
            System.out.println("sorted " + descendingSorted);

            //filter
            Map <Integer, Long> filtered = new LinkedHashMap<>();
            descendingSorted.entrySet().stream()
                .filter(x -> x.getValue() >= 2)
                .forEach(x -> filtered.put(x.getKey(), x.getValue()));;
            System.out.println("filtered " + filtered);

            //Split groups
            Map<Object, List<Entry<Integer, Long>>> groups = filtered.entrySet().stream()
                    .collect(Collectors.groupingBy(x -> x.getValue()));
            System.out.println("grouped " + groups);
    }
}

导致

group by value, count {1=3, 2=1, 3=4}
sorted {3=4, 1=3, 2=1}
filtered {3=4, 1=3}
grouped {3=[1=3], 4=[3=4]}

这是正确的,但我正在逐渐进入更深奥的数据结构,没有特别的意义,正如你所看到的,完成了(wtf?)Map&lt;Object, List&lt;Entry&lt;Integer, Long&gt;&gt;&gt;,如你所见。虽然它可以只是一个Map&lt;Int, Map&lt;Int, Int&gt;&gt;

所以具体的问题是,如何转换和包含流操作产生的数据结构输出?

我已经看到 Collectors 提供了对 Map(...) 的转换操作,我想这是要走的路,但我无法(由于缺乏适当的知识,我认为)让它工作。

在这种情况下,在我看来,我将通过教学解释、链接到综合资源以更好地理解流和函数式编程或类似的东西,而不是特定案例的实际解决方案(这将适合锻炼,但你明白了)

【问题讨论】:

    标签: java list dictionary set java-stream


    【解决方案1】:

    您在这里遇到困难有点令人惊讶,因为您已经展示了所有必要事物的知识。您知道groupingBy 可以获取另一个Collector,您已经命名了正确的toMap,并且您已经使用函数提取了Map.Entry 值。

    结合这些东西,给你

    Map<Long, Map<Integer, Long>> groups = filtered.entrySet().stream()
        .collect(Collectors.groupingBy(x -> x.getValue(),
            Collectors.toMap(x -> x.getKey(), x -> x.getValue())));
    System.out.println("grouped " + groups);
    

    为了更好的演示操作,我把输入改为

    List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 4);
    

    导致

    grouped {3=[1=3, 4=3], 4=[3=4]}
    

    尽管如此,重复与外部映射键始终相同的计数是没有意义的。所以另一种选择是

    Map<Long, List<Integer>> groups = filtered.entrySet().stream()
        .collect(Collectors.groupingBy(Map.Entry::getValue,
            Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
    System.out.println("grouped " + groups);
    

    导致

    grouped {3=[1, 4], 4=[3]}
    

    请注意,您不应在地图中使用 forEach/forEachOrderedput。你的中间步骤应该是

    //Sort desc
    Map<Integer, Long> descendingSorted = countGrouped.entrySet().stream()
        .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (a,b) -> { throw new AssertionError(); }, LinkedHashMap::new));
    System.out.println("sorted " + descendingSorted);
    
    //filter
    Map<Integer, Long> filtered = descendingSorted.entrySet().stream()
        .filter(x -> x.getValue() >= 2)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
            (a,b) -> { throw new AssertionError(); }, LinkedHashMap::new));
    System.out.println("filtered " + filtered);
    

    toMap 收集器接受映射工厂强制我们提供合并功能,但由于我们的输入已经是一个必须具有不同键的映射,所以我在这里提供了一个始终抛出的功能,因为如果出现严重错误,如果重复出现。

    但请注意,强制将所有这些操作收集到新地图中是不必要的复杂和低效。也没有必要先对整个数据进行排序,然后通过filter 减少数据量。首先过滤可能会减少排序步骤的工作,而过滤操作的结果不应该取决于顺序。

    在单个管道中完成整个操作要好得多

    List<Integer> list = Arrays.asList(1, 1, 1, 2, 3, 3, 3, 3, 4, 4, 4);
    
    Map<Integer, Long> countGrouped = list.stream().collect(
        Collectors.groupingBy(x -> x, Collectors.counting()));
    System.out.println("group by value, count " + countGrouped);
    
    Map<Long, List<Integer>> groups = countGrouped.entrySet().stream()
        .filter(x -> x.getValue() >= 2)
        .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
        .collect(Collectors.groupingBy(Map.Entry::getValue, LinkedHashMap::new, 
            Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
    
    System.out.println("grouped " + groups);
    

    注意,和之前的代码不同的是,现在最后的分组操作也会保留顺序,结果是

    grouped {4=[3], 3=[1, 4]}
    

    即,组按降序排序。

    由于计数是结果映射的键,我们也可以使用本质排序的映射作为结果类型并省略排序步骤:

    Map<Long, List<Integer>> groups = countGrouped.entrySet().stream()
        .filter(x -> x.getValue() >= 2)
        .collect(Collectors.groupingBy(Map.Entry::getValue,
            () -> new TreeMap<>(Comparator.<Long>reverseOrder()),
            Collectors.mapping(Map.Entry::getKey, Collectors.toList())));
    

    主要区别在于结果映射流操作之后的行为,例如如果您向其中插入更多元素,TreeMap 将根据降序插入新键,而LinkedHashMap 会将它们附加到末尾,保持插入顺序。

    【讨论】:

    • 漂亮,这就是我需要的那种精通。只是一个问题,将 foreach 放入 map 的具体问题是什么?
    • @Greco 使用流来修改现有对象应该是最后的手段,当没有其他解决方案可能时。根据特定的操作,它可能会给并行流或效率带来问题,但即使不是这样,使用 foreach 也可能成为一种习惯,从而阻碍了对直接解决方案的看法。
    【解决方案2】:

    groupingBy 的签名是public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier),但如果我理解正确,您只想将值映射到映射条目,例如:

    Map<Object, Map.Entry<Integer, Long>> groups = filtered.entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getValue, x -> x));
    System.out.println("grouped " + groups);
    

    输出

    grouped {3=1=3, 4=3=4}
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-23
      • 2013-12-20
      • 2018-11-23
      • 2019-07-30
      • 2011-04-21
      • 2022-01-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多