按元素出现次数对List分组,按键和值对Map排序

时间:2019-06-28 08:15:21

标签: java java-8 java-stream

我有一个输入数组 ["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}

有什么建议吗?而且必须使用单个stream()获得输出

3 个答案:

答案 0 :(得分: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);
}

答案 1 :(得分:1)

如果您可以使用第三方库,则可以在带有Eclipse Collections的流中使用它:


function cc(card) {
  // Only change code below this line
  if (card==2 || card==3 || card==4 || card==5 || card==6) {
    count+=1;
  }
  else if (card==7 || card==8 || card==9) {
count+=0;
  }
  else if (card==10 || card=="J" || card=="Q" || card=="K" || card=="A") {
    count-=1;
  }
  else {
  return "No such combination";
  }
  if (count>0) {
    return count + " " + "Bet";
  }
  else {
    return count + " " + "Hold";
  }
  // Only change code above this line
}

// Add/remove calls to test your function.
// Note: Only the last will display
cc(7); cc(8); cc(9);

输出:

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

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

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

注意:我是Eclipse Collections的提交者

答案 2 :(得分:0)

使用标准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));

发生了什么事

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

这似乎至少是在单个流操作中完成的。