【问题标题】:Java-get most common element in a listJava-获取列表中最常见的元素
【发布时间】:2021-04-10 07:17:23
【问题描述】:

Java 或 Guava 有什么东西可以返回列表中最常见的元素吗?

List<BigDecimal> listOfNumbers=  new ArrayList<BigDecimal>(); 

[1,3,4,3,4,3,2,3,3,3,3,3]

返回 3

【问题讨论】:

  • 如果有两个最常出现的元素怎么办?
  • 您确定这里需要 BigDecimal 吗?
  • 如果我正在工作是.....

标签: java guava


【解决方案1】:

In statistics, this is called the "mode"。一个普通的 Java 8 解决方案如下所示:

Stream.of(1, 3, 4, 3, 4, 3, 2, 3, 3, 3, 3, 3)
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      .entrySet()
      .stream()
      .max(Map.Entry.comparingByValue())
      .ifPresent(System.out::println);

产量:

3=8

jOOλ 是一个在流上支持mode() 的库。以下程序:

System.out.println(
    Seq.of(1, 3, 4, 3, 4, 3, 2, 3, 3, 3, 3, 3)
       .mode()
);

产量:

Optional[3]

为简单起见,我省略了使用BigDecimal。不过,解决方案是一样的。

(免责声明:我为 jOOλ 背后的公司工作)

【讨论】:

  • 这是最干净的例子,干得好
  • t -> t lambda 表达式可以替换为 Functions.identity()。我已经发布了一个带有适当更改的编辑提案。
  • @MarcinKłopotek:谢谢,是的,为什么不呢
【解决方案2】:

在集合中查找最频繁的项目:

private <V> V findMostFrequentItem(final Collection<V> items)
{
  return items.stream()
      .filter(Objects::nonNull)
      .collect(Collectors.groupingBy(Functions.identity(), Collectors.counting())).entrySet().stream()
      .max(Comparator.comparing(Entry::getValue))
      .map(Entry::getKey)
      .orElse(null);
}

【讨论】:

    【解决方案3】:

    我们只需一次迭代即可轻松完成:

    public static Integer mostFrequent(List<Integer> list) {
    
        if (list == null || list.isEmpty())
            return null;
    
        Map<Integer, Integer> counterMap = new HashMap<Integer, Integer>();
        Integer maxValue = 0;
        Integer mostFrequentValue = null;
    
        for(Integer valueAsKey : list) {
            Integer counter = counterMap.get(valueAsKey);
            counterMap.put(valueAsKey, counter == null ? 1 : counter + 1);
            counter = counterMap.get(valueAsKey);
            if (counter > maxValue) {
                maxValue = counter;
                mostFrequentValue = valueAsKey;
            }
        }
        return mostFrequentValue;
    }
    

    【讨论】:

      【解决方案4】:

      这是一个纯 Java 8 解决方案(注意:不要使用这个,见下文):

      List<Integer> theList = Arrays.asList(1, 3, 4, 3, 4, 3, 2, 3, 3, 3, 3, 3);
      Integer maxOccurredElement = theList.stream()
              .reduce(BinaryOperator.maxBy((o1, o2) -> Collections.frequency(theList, o1) -
                              Collections.frequency(theList, o2))).orElse(null);
      System.out.println(maxOccurredElement);
      

      另一种解决方案,通过按频率将元素收集到地图中,然后找到具有最大值的条目并返回其键(与arshajii's answer 上的解决方案基本相同,使用 Java 8 编写):

      Integer maxVal = theList.stream()
                      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                      .entrySet().stream().max((o1, o2) -> o1.getValue().compareTo(o2.getValue()))
                      .map(Map.Entry::getKey).orElse(null);
      

      更新:如果最频繁的元素不止一个,并且你想将它们全部放在一个集合中,我提出两种方法:

      方法A: 将原始集合收集到一个以键为元素、值为出现次数的映射后,获取最大值的条目并过滤与此值相等的映射条目我们找到的最大值(如果)。像这样的:

      Map<Integer, Long> elementCountMap = theList.stream()
              .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
      List<Integer> result = elementCountMap.values().stream()
              .max(Long::compareTo).map(maxValue -> elementCountMap.entrySet().stream()
                  .filter(entry -> maxValue.equals(entry.getValue())).map(Map.Entry::getKey).collect(Collectors.toList()))
              .orElse(Collections.emptyList());
      

      方法B:将原始集合收集到一个以key为元素,value为出现次数的map后,将这个map转换为以key为出现次数,value为出现次数的新map具有此出现次数的元素列表。然后使用比较键的自定义比较器找到此映射的最大元素,并获取此条目的值。像这样:

      List<Integer> result = theList.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
          .entrySet().stream()
          .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())))
          .entrySet().stream().max((o1, o2) -> o1.getKey().compareTo(o2.getKey())).map(Map.Entry::getValue)
          .orElse(Collections.emptyList());
      

      【讨论】:

      • 该算法具有O(N^2) 的复杂性,这在O(N) 中是可行的...
      • @LukasEder 是的,一遍又一遍地调用Collections.frequency() 是不可行的,我在写答案时没有考虑复杂性。编辑并添加了另一个解决方案,它具有O(N) 复杂性。
      • 非常好的替代解决方案!
      • 我喜欢第二种解决方案.. 但它是否处理多个元素具有相同频率的情况?我不知道,因为我是 Java 新手。如果不是,是否可以将其修改为仅返回具有相同出现次数的多个元素之一?
      • @Kai 不幸的是,它不能处理具有相同频率的多个元素的情况。见这里:stackoverflow.com/questions/29334404/… 但请看我对答案的更新,我也为这个案例添加了解决方案。
      【解决方案5】:

      这是 Louis 回答的扩展,它支持多个元素具有相同的最大出现次数的情况:

      private <T> List<T> getMostFrequentElements(List<T> list) {
          Multiset<T> multiset = HashMultiset.create(list);
      
          List<T> mostFrequents = new ArrayList<>();
          int maxCount = 0;
      
          for (Multiset.Entry<T> entry : multiset.entrySet()) {
              if (entry.getCount() > maxCount) {
                  maxCount = entry.getCount();
                  mostFrequents.clear();
                  mostFrequents.add(entry.getElement());
              } else if (entry.getCount() == maxCount) {
                  mostFrequents.add(entry.getElement());
              }
          }
      
          return mostFrequents;
      }
      

      【讨论】:

        【解决方案6】:

        Guava 提供了一个 method 来提供帮助,尽管它的效率不如 Louis 的解决方案。

        BigDecimal mostCommon = 
            Multisets.copyHighestCountFirst(ImmutableMultiset.copyOf(listOfNumbers))
                .iterator().next();
        

        【讨论】:

          【解决方案7】:

          可能最简单的 Guava 解决方案如下所示

          Multiset<BigDecimal> multiset = HashMultiset.create(listOfNumbers);
          BigDecimal maxElement = null;
          int maxCount = 0;
          for (Multiset.Entry<BigDecimal> entry : multiset.entrySet()) {
            if (entry.getCount() > maxCount) {
              maxElement = entry.getElement();
              maxCount = entry.getCount();
            }
          }
          

          这是一个完整的解决方案,比我所讨论的其他替代方案要短。

          【讨论】:

            【解决方案8】:

            如果你愿意使用 Google Guava,你可以使用它的MultiSet 类:

            MultiSet<BigNumber> numbers = HashMultiSet.create();
            numberSet.addAll(list);
            Set<MultiSet.Entry<BigNumber>> pairs = numbers.emtrySet();
            Set<MultiSet.Entry<BigNumber>> copies = new HashSet<MultiSet.Entry<BigNumber>>(pairs);
            

            现在,按 copies 的值降序排序。

            【讨论】:

              【解决方案9】:

              这很容易自己实现:

              public static <T> T mostCommon(List<T> list) {
                  Map<T, Integer> map = new HashMap<>();
              
                  for (T t : list) {
                      Integer val = map.get(t);
                      map.put(t, val == null ? 1 : val + 1);
                  }
              
                  Entry<T, Integer> max = null;
              
                  for (Entry<T, Integer> e : map.entrySet()) {
                      if (max == null || e.getValue() > max.getValue())
                          max = e;
                  }
              
                  return max.getKey();
              }
              

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

              如果您想处理出现多于一个最常见元素的情况,您可以扫描列表一次以确定最常见元素出现的次数,然后再次扫描列表,将这些元素放入设置并返回。

              【讨论】:

              • 如果输入列表为空,return 语句将导致 NullPointerException。即使你不希望永远是空的,这样的事情也会更安全:return max == null ? null : max.getKey();
              • 这会忽略列表可能包含 n 个不同元素的情况。在这种情况下,没有最常见的元素。 if(map.size() == list.size()){return null;}
              【解决方案10】:

              执行此操作的经典方法是对列表进行排序,然后逐一处理:

              public static BigInteger findMostCommon(List<BigInteger> list) {
                  Collections.sort(list);
                  BigInteger mostCommon = null;
                  BigInteger last = null;
                  int mostCount = 0;
                  int lastCount = 0;
                  for (BigInteger x : list) {
                      if (x.equals(last)) {
                          lastCount++;
                      } else if (lastCount > mostCount) {
                          mostCount = lastCount;
                          mostCommon = last;
                      }
                      last = x;
                  }
                  return mostCommon;
              }
              

              这比使用散列来计算计数更节省空间,因为它对数组进行了适当的排序。你可以把它扔进一个泛型类并用 T 替换 BigInteger,或者只使用 Object 代替 BigInteger。

              【讨论】:

              • 该算法具有O(N * log N) 的复杂性,可以在O(N) 中完成。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-18
              • 1970-01-01
              • 2010-12-03
              • 2018-09-27
              相关资源
              最近更新 更多