【问题标题】:Group the List by element occurence count, sort the Map by key and value [closed]按元素出现计数对列表进行分组,按键和值对地图进行排序[关闭]
【发布时间】:2019-06-28 08:15:21
【问题描述】:

我有一个输入数组 ["Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco"]

预期的输出应该是

{Iveco=2, Mercedes=2, Skoda=2, MAN=1, Setra=1, Volvo=1}

表示以Vehicle 品牌为键、以值作为其出现次数的地图以及类似值的元素应按键的字母顺序和值排序。

我试过了

public static String busRanking(List<String> busModels) {
    Map<String, Long> counters = busModels.stream().skip(1).filter(Objects::nonNull)
            .filter(bus ->  !bus.startsWith("V"))
             .collect(Collectors.groupingBy(bus-> bus, Collectors.counting()));
    Map<String, Long> finalMap = new LinkedHashMap<>(); 
    counters.entrySet().stream()
                       .sorted(Map.Entry.comparingByValue(
                                      Comparator.reverseOrder()))
                       .forEachOrdered(
                                  e -> finalMap.put(e.getKey(), 
                                                    e.getValue()));
    return finalMap.toString();
}

public static void main(String[] args) {
    List<String> busModels = Arrays.asList( "Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco");
    String busRanking = busRanking(busModels);
    System.out.println(busRanking);
}

我得到的输出 {Skoda=2, Mercedes=2, Iveco=2, Setra=1, MAN=1}

有什么建议吗?并且必须使用单流()获得输出

【问题讨论】:

  • 为什么在过滤掉所有以V 开头的内容后,Volvo 会是一个没有值的无效映射条目?
  • ... 似乎您正在使用 sorted 操作中的键查找 thenComparing
  • 它仍然在您的预期输出中
  • @BhabadyutiBal 您可以随时编辑问题以改进那里的信息。根据上述评论编辑了预期的输出。

标签: java java-8 java-stream


【解决方案1】:

我认为不错的解决方案是:

public static void main(String... args) {
    List<String> busModels = Arrays.asList( "Setra","Mercedes","Volvo","Mercedes","Skoda","Iveco","MAN",null,"Skoda","Iveco");

    Map<String, Long> collect = busModels.stream()
        .filter(Objects::nonNull)
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

    TreeMap<String, Long> stringLongTreeMap = new TreeMap<>(collect);

    Set<Map.Entry<String, Long>> entries = stringLongTreeMap.entrySet();

    ArrayList<Map.Entry<String, Long>> list = new ArrayList<>(entries);

    list.sort((o1, o2) -> o2.getValue().compareTo(o1.getValue()));

    String busRanking = list.toString();

    System.out.println(busRanking);
}

【讨论】:

  • 您的输出:{Iveco=2, MAN=1, Mercedes=2, Setra=1, Skoda=2, Volvo=1} 我需要 {Iveco=2, Mercedes=2, Skoda=2 , MAN=1, Setra=1, 沃尔沃=1}
  • @BhabadyutiBal 但是它不是在一个流转换中完成的。我不相信这是可能的。见stackoverflow.com/questions/39013437/…。如果这不是您的要求,我们可以从您的问题中删除它吗?
  • 要求与单流有关
  • 似乎无法使用 Collectors.groupingBy,因为您必须在不消耗 Stream 的情况下收集它。什么是不可能的。
【解决方案2】:

如果您可以使用第三方库,这应该可以使用带有Eclipse Collections 的 Streams:

Comparator<ObjectIntPair<String>> comparator = 
    Comparators.fromFunctions(each -> -each.getTwo(), ObjectIntPair::getOne);

String[] strings = {"Setra", "Mercedes", "Volvo", "Mercedes", "Skoda", "Iveco", 
    "MAN", null, "Skoda", "Iveco"};

List<ObjectIntPair<String>> pairs = Stream.of(strings).collect(Collectors2.toBag())
        .select(Predicates.notNull())
        .collectWithOccurrences(PrimitiveTuples::pair, Lists.mutable.empty())
        .sortThis(comparator);

System.out.println(pairs);

输出:

[Iveco:2, Mercedes:2, Skoda:2, MAN:1, Setra:1, Volvo:1]

这也可以通过不使用 Streams 来稍微简化。

List<ObjectIntPair<String>> pairs = Bags.mutable.with(strings)
        .select(Predicates.notNull())
        .collectWithOccurrences(PrimitiveTuples::pair, Lists.mutable.empty())
        .sortThis(comparator);

注意:我是 Eclipse Collections 的提交者

【讨论】:

    【解决方案3】:

    使用标准 Java API 的“单线”:

    Map<String, Long> map = busModels.stream()
        .filter(Objects::nonNull)
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
        .entrySet().stream()
        .sorted(Comparator
            .comparing((Entry<String, Long> e) -> e.getValue()).reversed()
            .thenComparing(e -> e.getKey()))
        .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (left, right) -> left + right, LinkedHashMap::new));
    

    会发生什么:

    • 它首先过滤掉空值。
    • 然后它收集到一个地图中,按出现次数分组。
    • 然后它遍历结果映射的条目,以便首先按值对其进行排序,然后比较键(因此,如果两次出现的次数相同,则依维柯排在 MAN 之前)。
    • 最后,我们将条目提取到 LinkedHashMap 中,这会保留插入顺序。

    输出:

    {Iveco=2, Mercedes=2, Skoda=2, MAN=1, Setra=1, Volvo=1}
    

    这使用单个方法链接语句。但是,我不会将此称为单个流操作,因为实际上创建了两个流,一个用于收集事件,另一个用于排序条目集。

    流通常不会被设计为多次遍历,至少在标准 Java API 中不会。

    可以用 StreamEx 完成吗?

    有一个名为StreamEx 的库,它可能包含一个允许我们使用单个流操作来完成它的方法。

    Map<String, Long> map = StreamEx.of(buses)
        .filter(Objects::nonNull)
        .map(t -> new AbstractMap.SimpleEntry<>(t, 1L))
        .sorted(Comparator.comparing(Map.Entry::getKey))
        .collapse(Objects::equals, (left, right) -> new AbstractMap.SimpleEntry<>(left.getKey(), left.getValue() + right.getValue()))
        .sorted(Comparator
            .comparing((Entry<String, Long> e) -> e.getValue()).reversed()
            .thenComparing(e -> e.getKey()))
        .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (left, right) -> left + right, LinkedHashMap::new));
    

    会发生什么:

    • 我们过滤掉空值。
    • 我们将其映射到Entrys 流,每个键是总线名称,每个值是初始出现次数,即1
    • 然后我们按每个键对流进行排序,确保所有相同的总线在流中都是相邻的。
    • 这允许我们使用collapse 方法,该方法使用合并函数合并满足给定谓词的一系列相邻元素。所以Mercedes =&gt; 1 + Mercedes =&gt; 1 变成了Mercedes =&gt; 2
    • 然后我们如上所述进行分类和收集。

    这至少似乎是在单个流操作中进行的。

    【讨论】:

    • 是的,但仍然是两个流。问题是“必须使用单流()获得输出”,我认为这是不可能的。
    • @ŁukaszRzeszotarski 我已经更新了帖子。
    猜你喜欢
    • 1970-01-01
    • 2020-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多