【问题标题】:How to count the number of occurrences of an element in a List如何计算List中某个元素出现的次数
【发布时间】:2010-10-05 02:10:41
【问题描述】:

我有一个ArrayList,一个Java的Collection类,如下:

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");

如您所见,animalsArrayList 由 3 个 bat 元素和 1 个 owl 元素组成。我想知道 Collection 框架中是否有返回 bat 出现次数的 API,或者是否有其他方法来确定出现次数。

我发现 Google 的 Collection Multiset 确实有一个 API 可以返回元素出现的总次数。但这仅与 JDK 1.5 兼容。我们的产品目前在JDK 1.6,所以我不能使用它。

【问题讨论】:

  • 这就是为什么您应该对接口而不是实现进行编程的原因之一。如果您碰巧找到了正确的集合,则需要更改类型以使用该集合。我会对此发表答案。

标签: java arraylist collections


【解决方案1】:
 Integer[] spam = new Integer[]  {1,2,2,3,4};
 List<Integer>   list=Arrays.asList(spam);

System.out.println(list.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting())));
System.out.println(list.stream().collect(Collectors.groupingBy(Function.identity(),HashMap::new,Collectors.counting())));
    

输出

{1=1, 2=2, 3=1, 4=1}

【讨论】:

    【解决方案2】:

    要实现这一点,可以通过多种方式实现,即:

    返回单个元素出现次数的方法:

    Collection Frequency

    Collections.frequency(animals, "bat");
    

    Java 流:

    过滤器

    animals.stream().filter("bat"::equals).count();
    

    只是迭代认为列表

    public static long manually(Collection<?> c, Object o){
        int count = 0;
        for(Object e : c)
            if(e.equals(o))
                count++;
        return count;
    }
    

    创建频率图的方法:

    Collectors.groupingBy

    Map<String, Long> counts = 
           animals.stream()
                  .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
    

    merge

    Map<String, Long> map = new HashMap<>();
    c.forEach(e -> map.merge(e, 1L, Long::sum));
    

    手动

    Map<String, Integer> mp = new HashMap<>();
            animals.forEach(animal -> mp.compute(animal, (k, v) -> (v == null) ? 1 : v + 1));
    

    一个包含所有方法的运行示例:

    import java.util.*;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    public class Frequency {
    
        public static int frequency(Collection<?> c, Object o){
            return Collections.frequency(c, o);
        }
    
        public static long filter(Collection<?> c, Object o){
            return c.stream().filter(o::equals).count();
        }
    
        public static long manually(Collection<?> c, Object o){
            int count = 0;
            for(Object e : c)
                if(e.equals(o))
                    count++;
            return count;
        }
    
        public static Map<?, Long> mapGroupBy(Collection<?> c){
            return c.stream()
                    .collect(Collectors.groupingBy(Function.identity() , Collectors.counting()));
        }
    
        public static Map<Object, Long> mapMerge(Collection<?> c){
            Map<Object, Long> map = new HashMap<>();
            c.forEach(e -> map.merge(e, 1L, Long::sum));
            return map;
        }
    
        public static Map<Object, Long> manualMap(Collection<?> c){
            Map<Object, Long> map = new HashMap<>();
            c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
            return map;
        }
    
    
        public static void main(String[] args){
            List<String> animals = new ArrayList<>();
            animals.add("bat");
            animals.add("owl");
            animals.add("bat");
            animals.add("bat");
    
            System.out.println(frequency(animals, "bat"));
            System.out.println(filter(animals,"bat"));
            System.out.println(manually(animals,"bat"));
            mapGroupBy(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
            mapMerge(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
            manualMap(animals).forEach((k, v) -> System.out.println(k + " -> "+v));
        }
    }
    

    方法名称应该反映了这些方法正在做什么,但是,我使用名称来反映正在使用的方法(假设在当前上下文中它是好的)。

    【讨论】:

      【解决方案3】:

      您可以将 Java 8 的 groupingBy 功能用于您的用例。

      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      import java.util.function.Function;
      import java.util.stream.Collectors;
      
      public class Test {
          public static void main(String[] args) {
              List<String> animals = new ArrayList<>();
      
              animals.add("bat");
              animals.add("owl");
              animals.add("bat");
              animals.add("bat");
      
              Map<String,Long> occurrenceMap =
                      animals.stream().collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
              System.out.println("occurrenceMap:: " + occurrenceMap);
          }
      }
      

      输出

      occurrenceMap:: {bat=3, owl=1}
      
      

      【讨论】:

        【解决方案4】:

        ​如果你使用Eclipse Collections,你可以使用Bag。通过调用toBag(),可以从RichIterable 的任何实现中返回MutableBag

        MutableList<String> animals = Lists.mutable.with("bat", "owl", "bat", "bat");
        MutableBag<String> bag = animals.toBag();
        Assert.assertEquals(3, bag.occurrencesOf("bat"));
        Assert.assertEquals(1, bag.occurrencesOf("owl"));
        

        Eclipse Collections 中的HashBag 实现由MutableObjectIntMap 提供支持。

        注意:我是 Eclipse Collections 的提交者。

        【讨论】:

          【解决方案5】:
          package traversal;
          
          import java.util.ArrayList;
          import java.util.List;
          
          public class Occurrance {
              static int count;
          
              public static void main(String[] args) {
                  List<String> ls = new ArrayList<String>();
                  ls.add("aa");
                  ls.add("aa");
                  ls.add("bb");
                  ls.add("cc");
                  ls.add("dd");
                  ls.add("ee");
                  ls.add("ee");
                  ls.add("aa");
                  ls.add("aa");
          
                  for (int i = 0; i < ls.size(); i++) {
                      if (ls.get(i) == "aa") {
                          count = count + 1;
                      }
                  }
                  System.out.println(count);
              }
          }
          

          输出:4

          【讨论】:

            【解决方案6】:
            Map<String,Integer> hm = new HashMap<String, Integer>();
            for(String i : animals) {
                Integer j = hm.get(i);
                hm.put(i,(j==null ? 1 : j+1));
            }
            for(Map.Entry<String, Integer> val : hm.entrySet()) {
                System.out.println(val.getKey()+" occurs : "+val.getValue()+" times");
            }
            

            【讨论】:

              【解决方案7】:

              使用 Java 8 特性查找数组中字符串值出现的简单方法。

              public void checkDuplicateOccurance() {
                      List<String> duplicateList = new ArrayList<String>();
                      duplicateList.add("Cat");
                      duplicateList.add("Dog");
                      duplicateList.add("Cat");
                      duplicateList.add("cow");
                      duplicateList.add("Cow");
                      duplicateList.add("Goat");          
                      Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString(),Collectors.counting()));
                      System.out.println(couterMap);
                  }
              

              输出:{Cat=2, Goat=1, Cow=1, cow=1, Dog=1}

              您会注意到“Cow”和cow 不被视为相同的字符串,如果您需要相同的计数,请使用.toLowerCase()。请在下面找到相同的 sn-p。

              Map<String, Long> couterMap = duplicateList.stream().collect(Collectors.groupingBy(e -> e.toString().toLowerCase(),Collectors.counting()));
              

              输出:{cat=2, cow=2, goat=1, dog=1}

              【讨论】:

              • nit:因为列表是字符串列表,toString() 是不必要的。你可以这样做:duplicateList.stream().collect(Collectors.groupingBy(e -&gt; e,Collectors.counting()));
              【解决方案8】:
              List<String> lst = new ArrayList<String>();
              
              lst.add("Ram");
              lst.add("Ram");
              lst.add("Shiv");
              lst.add("Boss");
              
              Map<String, Integer> mp = new HashMap<String, Integer>();
              
              for (String string : lst) {
              
                  if(mp.keySet().contains(string))
                  {
                      mp.put(string, mp.get(string)+1);
              
                  }else
                  {
                      mp.put(string, 1);
                  }
              }
              
              System.out.println("=mp="+mp);
              

              输出:

              =mp= {Ram=2, Boss=1, Shiv=1}
              

              【讨论】:

                【解决方案9】:
                List<String> list = Arrays.asList("as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd", "as", "asda",
                        "asd", "urff", "dfkjds", "hfad", "asd", "qadasd" + "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd",
                        "qadasd", "as", "asda", "asd", "urff", "dfkjds", "hfad", "asd", "qadasd");
                

                方法一:

                Set<String> set = new LinkedHashSet<>();
                set.addAll(list);
                
                for (String s : set) {
                
                    System.out.println(s + " : " + Collections.frequency(list, s));
                }
                

                方法二:

                int count = 1;
                Map<String, Integer> map = new HashMap<>();
                Set<String> set1 = new LinkedHashSet<>();
                for (String s : list) {
                    if (!set1.add(s)) {
                        count = map.get(s) + 1;
                    }
                    map.put(s, count);
                    count = 1;
                
                }
                System.out.println(map);
                

                【讨论】:

                • 欢迎来到 Stack Overflow!考虑解释您的代码,以便其他人更容易理解您的解决方案。
                【解决方案10】:

                Java 中没有本地方法可以为您做到这一点。但是,您可以使用 Apache Commons-Collections 中的 IterableUtils#countMatches() 为您完成此操作。

                【讨论】:

                • 请参考我下面的答案——正确的答案是使用从一开始就支持计数思想的结构,而不是每次进行查询时从头到尾计数条目。
                • @mP 所以你只是对所有与你有不同意见的人投反对票?如果他由于某种原因不能使用 Bag 或者被困在使用其中一个原生 Collections 怎么办?
                • -1 是一个痛苦的失败者 :-) 我认为 mP 否决了你,因为每次你想要一个结果时,你的解决方案都会花费时间。一个袋子只在插入时花费一点时间。与数据库一样,这类结构往往“读多于写”,因此使用低成本选项是有意义的。
                • 而且您的回答似乎也需要非本地的东西,所以您的评论似乎有点奇怪。
                • 感谢你们两位。我相信这两种方法中的一种或两种方法都可能有效。我明天试试看。
                【解决方案11】:

                我不想让这种情况变得更加困难,并使用两个迭代器来实现 我有一个 LastName -> FirstName 的 HashMap。我的方法应该删除具有重复名字的项目。

                public static void removeTheFirstNameDuplicates(HashMap<String, String> map)
                {
                
                    Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator();
                    Iterator<Map.Entry<String, String>> iter2 = map.entrySet().iterator();
                    while(iter.hasNext())
                    {
                        Map.Entry<String, String> pair = iter.next();
                        String name = pair.getValue();
                        int i = 0;
                
                        while(iter2.hasNext())
                        {
                
                            Map.Entry<String, String> nextPair = iter2.next();
                            if (nextPair.getValue().equals(name))
                                i++;
                        }
                
                        if (i > 1)
                            iter.remove();
                
                    }
                
                }
                

                【讨论】:

                  【解决方案12】:

                  直接从列表中获取对象的出现次数:

                  int noOfOccurs = Collections.frequency(animals, "bat");
                  

                  要获取对象集合在列表中的出现,将对象类中的equals方法重写为:

                  @Override
                  public boolean equals(Object o){
                      Animals e;
                      if(!(o instanceof Animals)){
                          return false;
                      }else{
                          e=(Animals)o;
                          if(this.type==e.type()){
                              return true;
                          }
                      }
                      return false;
                  }
                  
                  Animals(int type){
                      this.type = type;
                  }
                  

                  将 Collections.frequency 称为:

                  int noOfOccurs = Collections.frequency(animals, new Animals(1));
                  

                  【讨论】:

                    【解决方案13】:

                    使用 Streams 的替代 Java 8 解决方案:

                    long count = animals.stream().filter(animal -> "bat".equals(animal)).count();
                    

                    【讨论】:

                      【解决方案14】:

                      Java 8 - 另一种方法

                      String searched = "bat";
                      long n = IntStream.range(0, animals.size())
                                  .filter(i -> searched.equals(animals.get(i)))
                                  .count();
                      

                      【讨论】:

                        【解决方案15】:

                        实际上,Collections 类有一个名为 frequency(Collection c, Object o) 的静态方法,它返回您正在搜索的元素的出现次数,顺便说一下,这将完美地工作给你:

                        ArrayList<String> animals = new ArrayList<String>();
                        animals.add("bat");
                        animals.add("owl");
                        animals.add("bat");
                        animals.add("bat");
                        System.out.println("Freq of bat: "+Collections.frequency(animals, "bat"));
                        

                        【讨论】:

                        • Lars Andren 比你早 5 年发布了同样的答案。
                        【解决方案16】:

                        在 Java 8 中:

                        Map<String, Long> counts =
                            list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
                        

                        【讨论】:

                        • 使用 Function.identity() (带有静态导入)而不是 e -> e 使它更易于阅读。
                        • 为什么这比Collections.frequency()好?它似乎不太可读。
                        • 这不是我们所要求的。它做的工作比必要的要多。
                        • 这可能比要求的更多,但它正是我想要的(获取列表中不同元素的映射到它们的计数)。此外,当我搜索时,这个问题是谷歌的最高结果。
                        • @rozina 您一次获得所有计数。
                        【解决方案17】:

                        我很确定 Collections 中的静态频率方法在这里会派上用场:

                        int occurrences = Collections.frequency(animals, "bat");
                        

                        无论如何我都会这样做。我很确定这是 jdk 1.6。

                        【讨论】:

                        【解决方案18】:

                        如果您是我的ForEach DSL 的用户,可以使用Count 查询来完成。

                        Count<String> query = Count.from(list);
                        for (Count<Foo> each: query) each.yield = "bat".equals(each.element);
                        int number = query.result();
                        

                        【讨论】:

                          【解决方案19】:

                          这说明了为什么“Refer to objects by their interfaces”很重要,如Effective Java 书中所述。

                          如果您对实现进行编码并在代码中的 50 个位置使用 ArrayList,当您找到一个很好的“列表”实现来计算项目时,您将不得不更改所有这 50 个位置,并且可能您必须要破坏你的代码(如果它只被你使用的话没什么大不了的,但是如果它被别人使用,你也会破坏他们的代码)

                          通过对接口进行编程,您可以让这 50 个地方保持不变,并将实现从 ArrayList 替换为“CountItemsList”(例如)或其他一些类。

                          下面是一个关于如何编写的非常基本的示例。这只是一个示例,生产就绪列表会要复杂得多

                          import java.util.*;
                          
                          public class CountItemsList<E> extends ArrayList<E> { 
                          
                              // This is private. It is not visible from outside.
                              private Map<E,Integer> count = new HashMap<E,Integer>();
                          
                              // There are several entry points to this class
                              // this is just to show one of them.
                              public boolean add( E element  ) { 
                                  if( !count.containsKey( element ) ){
                                      count.put( element, 1 );
                                  } else { 
                                      count.put( element, count.get( element ) + 1 );
                                  }
                                  return super.add( element );
                              }
                          
                              // This method belongs to CountItemList interface ( or class ) 
                              // to used you have to cast.
                              public int getCount( E element ) { 
                                  if( ! count.containsKey( element ) ) {
                                      return 0;
                                  }
                                  return count.get( element );
                              }
                          
                              public static void main( String [] args ) { 
                                  List<String> animals = new CountItemsList<String>();
                                  animals.add("bat");
                                  animals.add("owl");
                                  animals.add("bat");
                                  animals.add("bat");
                          
                                  System.out.println( (( CountItemsList<String> )animals).getCount( "bat" ));
                              }
                          }
                          

                          此处应用的 OO 原则:继承、多态、抽象、封装。

                          【讨论】:

                          • 那么人们应该总是尝试组合而不是继承。当您有时可能需要 LinkedList 或其他时,您的实现现在被困在 ArrayList 上。您的示例应该在其构造函数/工厂中采用另一个 LIst 并返回一个包装器。
                          • 我完全同意你的看法。我在示例中使用继承的原因是因为使用继承显示运行示例比组合更容易(必须实现 List 接口)。继承创造了最高的耦合。
                          • 但是通过将它命名为 CountItemsList 意味着它做了两件事,它计算项目并且它是一个列表。我认为该类的一个单一职责(计算出现次数)会很简单,您不需要实现 List 接口。
                          【解决方案20】:

                          一种更有效的方法可能是

                          Map<String, AtomicInteger> instances = new HashMap<String, AtomicInteger>();
                          
                          void add(String name) {
                               AtomicInteger value = instances.get(name);
                               if (value == null) 
                                  instances.put(name, new AtomicInteger(1));
                               else
                                  value.incrementAndGet();
                          }
                          

                          【讨论】:

                            【解决方案21】:

                            所以用老式的方式做你自己的:

                            Map<String, Integer> instances = new HashMap<String, Integer>();
                            
                            void add(String name) {
                                 Integer value = instances.get(name);
                                 if (value == null) {
                                    value = new Integer(0);
                                    instances.put(name, value);
                                 }
                                 instances.put(name, value++);
                            }
                            

                            【讨论】:

                            • 如果需要,使用适当的“同步”来避免竞争条件。但我仍然希望在它自己的类中看到它。
                            • 你有一个错字。需要 HashMap 代替,因为您在 Map 中使用它。但是用 0 代替 1 的错误有点严重。
                            【解决方案22】:

                            将arraylist的元素放入hashMap中统计频率。

                            【讨论】:

                            • 这与tweakt 在代码示例中所说的完全一样。
                            【解决方案23】:

                            我想知道,为什么不能在 JDK 1.6 中使用 Google 的 Collection API。是这样说的吗?我想你可以,不应该有任何兼容性问题,因为它是为较低版本构建的。如果它是为 1.6 构建的并且您运行的是 1.5,那么情况会有所不同。

                            我是不是哪里错了?

                            【讨论】:

                            • 他们已经明确提到他们正在将他们的api升级到jdk 1.6。
                            • 这不会使旧的不兼容。是吗?
                            • 不应该。但是他们抛出免责声明的方式让我在他们的 0.9 版本中使用它感到不舒服
                            • 我们在 1.6 中使用它。哪里说只兼容1.5?
                            • “升级到 1.6”可能是指“升级以利用 1.6 中的新内容”,而不是“修复与 1.6 的兼容性”。
                            【解决方案24】:

                            你想要的是一个包——它就像一个集合,但也计算出现次数。不幸的是,java Collections 框架 - 很棒,因为它们没有 Bag impl。为此,必须使用 Apache Common Collection link text

                            【讨论】:

                            • 最好的可扩展解决方案,如果你不能使用第三方的东西,那就自己写吧。袋子不是火箭科学来创造的。 +1。
                            • 由于给出了一些模糊的答案而被否决,而其他人则提供了频率计数数据结构的实现。您链接到的“袋子”数据结构也不是 OP 问题的合适解决方案;该“袋子”结构旨在保存特定数量的令牌副本,而不是计算令牌的出现次数。
                            【解决方案25】:

                            抱歉,没有简单的方法调用可以做到这一点。不过,您需要做的就是创建一个地图并用它计算频率。

                            HashMap<String,int> frequencymap = new HashMap<String,int>();
                            foreach(String a in animals) {
                              if(frequencymap.containsKey(a)) {
                                frequencymap.put(a, frequencymap.get(a)+1);
                              }
                              else{ frequencymap.put(a, 1); }
                            }
                            

                            【讨论】:

                            • 这确实不是一个可扩展的解决方案 - 假设 MM 的数据集有成百上千的条目,而 MM 想知道每个条目的频率。这可能是一项非常昂贵的任务 - 尤其是当有更好的方法时。
                            • @dehmann,我不认为他真的想要蝙蝠在 4 元素集合中出现的次数,我认为这只是样本数据,所以我们会更好地理解 :-)。跨度>
                            • @Vinegar 2/2。编程就是现在正确地做事,所以我们不会给其他人带来头痛或糟糕的体验,无论是未来的用户还是其他编码人员。 PS:你写的代码越多,出错的可能性就越大。
                            • @mP:请解释为什么这不是一个可扩展的解决方案。 Ray Hidayat 正在为每个令牌构建频率计数,以便随后可以查找每个令牌。有什么更好的解决方案?
                            • 这看起来像 C#,但问题标记为 java
                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多