如何计算列表中元素的出现次数


173

我有一个ArrayListJava的Collection类,如下所示:

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

如您所见,animals ArrayList包含3个bat元素和一个owl元素。我想知道Collection框架中是否有任何API返回出现次数,bat或者是否有另一种方法来确定出现次数。

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


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

Answers:


333

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

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

无论如何,这就是我要做的。我很确定这是jdk 1.6。


始终喜欢JRE中的Api,它会给项目添加另一个依赖项。而且不要重新发明轮子!
费尔南多。

它是在JDK 5中引入(虽然没有人在此之前使用的版本,所以它并不重要)docs.oracle.com/javase/8/docs/technotes/guides/collections/...
爪牙吉姆

104

在Java 8中:

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

6
使用Function.identity()(带有静态导入)代替e-> e使其可读性更好。
库奇2015年

8
为什么比这更好Collections.frequency()?似乎可读性较差。
rozina

这不是要求的。它所做的工作超出了必要。
亚历克斯·沃登

8
这可能比要求的功能要多,但它确实可以实现我想要的功能(获取列表中不同元素的映射以计数)。此外,此问题是我搜索时在Google中排名第一的结果。
KJP

@rozina一口气获得所有计数。
atoMerz

22

这表明,为什么按有效Java书中所述“ 通过其接口引用对象 ”很重要。

如果您对实现进行编码,并在代码中使用ArrayList,例如,在代码中使用了50个位置,那么当您找到一个很好的“ List”实现来对项目进行计数时,您将不得不更改所有这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原则在这里适用:继承,多态性,抽象,封装。


12
好吧,应该总是尝试合成而不是继承。现在,当您有时需要LinkedList或其他方法时,您的实现将停留在ArrayList上。您的示例应该在其构造函数/工厂中使用另一个LIst并返回一个包装器。
mP。

我完全同意你的观点。我在示例中使用继承的原因是,相比于组合(必须实现List接口),使用继承显示正在运行的示例要容易得多。继承产生最高的耦合。
OscarRyz

2
但是,通过将其命名为CountItemsList意味着您暗示它做了两件事,它对项目进行计数,并且它是一个列表。我认为,对该类负责,只需对发生的事件进行计数,就很简单,并且您无需实现List接口。
flob 2015年

11

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

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想知道每个条目的频率。这可能是一项非常昂贵的任务-尤其是当有更好的方法来执行此任务时。
mP。

是的,这可能不是一个好的解决方案,并不意味着它是错误的。
Adeel Ansari

1
@dehmann,我不认为他从字面上想知道4-元素集合中蝙蝠出现的次数,我认为那只是示例数据,所以我们会更好地理解:-)。
paxdiablo,

2
@醋2/2。编程是指现在要正确执行操作,因此,无论将来是用户还是其他编码器,我们都不会给其他人造成头痛或不好的体验。PS:您编写的代码越多,出现问题的机会就越大。
mP。

2
@mP:请解释为什么这不是可扩展的解决方案。Ray Hidayat正在为每个令牌建立频率计数,以便随后可以查找每个令牌。有什么更好的解决方案?
stackoverflowuser2010 2013年

10

Java中没有本机方法可以为您执行此操作。但是,您可以使用Apache Commons-Collections中的IterableUtils#countMatches()为您完成此操作。


请参阅下面的答案-正确的答案是使用一种从头开始支持计数思想的结构,而不是每次进行查询时都从头到尾计数条目。
mP。

@mP因此,您只是对与您有不同意见的所有人投反对票?如果他由于某种原因不能使用Bag或被限制使用本机Collections怎么办?
凯文”于2009年

-1是个痛苦的失败者:-)我认为mP挫败了您,因为每次您想要结果时,解决方案都会花费时间。一个袋子只在插入时花费一点时间。像数据库一样,这类结构倾向于“读取多于写入”,因此使用低成本选项很有意义。
paxdiablo,

而且您的回答似乎也需要非母语的内容,因此您的评论似乎有些奇怪。
paxdiablo,

多亏你们两个。我相信这两种方法中的一种或两种都可能有效。明天我会尝试。
MM。

9

实际上,Collections类有一个静态方法,称为:frequency(集合c,对象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"));

27
拉尔斯·安德伦(Lars Andren)在您的答案发布5年之前发布了相同的答案。
Fabian Barney

9

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

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

8

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

我在某个地方错了吗?


他们明确提到他们正在将其api升级到jdk 1.6。
MM。

1
那不会使旧的不兼容。可以?
Adeel Ansari

它不应该。但他们投掷免责声明的方式,让我不舒服在其0.9版使用它
MM。

我们在1.6中使用它。它在哪里说仅与1.5兼容?
帕特里克

2
“升级到1.6”可能意味着“升级以利用1.6中的新功能”,而不是“修复与1.6的兼容性”。
亚当·贾斯基维奇

6

一种稍微有效的方法可能是

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

6

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

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

要获得列表中的Object集合的出现,请重写Object类中的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));

6

使用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()。请在下面找到相同的代码段。

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

输出:{猫= 2,牛= 2,山羊= 1,狗= 1}


nit:因为该列表是字符串列表,toString()所以不必要。您可以这样做:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
塔德

5

您想要的是一个袋子-它就像一个集合,但也可以计算出现的次数。不幸的是,java Collections框架-很棒,因为它们没有Bag impl。为此,必须使用Apache Common Collection 链接文本


1
最佳可扩展解决方案,如果您不能使用第三方工具,请自己编写。提包并不是火箭科学。+1。
paxdiablo,

因给出一些模糊的答案而被低估,而其他人则为频率计数数据结构提供了实现。您链接到的“袋子”数据结构也不是解决OP问题的合适解决方案。该“袋子”结构旨在保存特定数量的令牌副本,而不是计算令牌的出现次数。
stackoverflowuser2010 2013年

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

方法1:

Set<String> set = new LinkedHashSet<>();
set.addAll(list);

for (String s : set) {

    System.out.println(s + " : " + Collections.frequency(list, s));
}

方法2:

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!考虑解释您的代码,以使其他人更容易理解您的解决方案。

2

如果使用Eclipse Collections,则可以使用Bag。一个MutableBag可以从任何实现返回RichIterable调用toBag()

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

HashBagEclipse Collections中的实现由来支持MutableObjectIntMap

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


1

将arraylist的元素放在hashMap中以计算频率。


这与tweakt在代码示例中所说的完全相同。
mP。

1

Java 8-另一种方法

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

0

因此,以老式的方式进行并自己滚动:

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

与适当的“同步”,如果需要,可以避免比赛条件。但是我还是更愿意在自己的班级上看到它。
paxdiablo,

你有错字 就像在Map中一样,需要HashMap。但是用0代替1的错误更加严重。
Adeel Ansari

0

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

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

0

我不想让这种情况变得更加困难,并且让它具有两个迭代器,我有一个具有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();

    }

}

0
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}

0
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");
}

0
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


最好在Stack Overflow上添加一个解释,说明为什么您的解决方案应该工作或比现有解决方案更好。有关更多信息,请阅读“ 如何回答”
塞缪尔刘氏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.