将group <By>值分组后,将Map <k,v>返回到Map <v,map <k,v >>,而不是Map <obj,list <entry <k,v =“” >>>

时间:2019-05-24 07:57:41

标签: java list dictionary set java-stream

我在维护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<Object, List<Entry<Integer, Long>>>完成。虽然只能是Map<Int, Map<Int, Int>>

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

我已经看到收集器向Map(...)提供转换操作,并且我想这是可行的方法,但是(由于缺乏适当的知识,我)无法使其工作。

在这种情况下,我认为通过讲解性的解释对我会大有帮助,可以将其链接到全面的资源以更好地理解流和函数式编程或类似的东西,而不是针对特定情况的实际解决方案(很适合做运动,但您明白了)

2 个答案:

答案 0 :(得分:2)

您在这里遇到困难令人惊讶,因为您已经展示了所有必要知识。您知道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会将它们附加到末尾,并保持插入顺序。

答案 1 :(得分:1)

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}