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

MM.*_*MM. 159 java collections arraylist

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

ArrayList<String> animals = new ArrayList<String>();
animals.add("bat");
animals.add("owl");
animals.add("bat");
animals.add("bat");
Run Code Online (Sandbox Code Playgroud)

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

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

Lar*_*ren 311

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

int occurrences = Collections.frequency(animals, "bat");
Run Code Online (Sandbox Code Playgroud)

无论如何我就是这样做的.我很确定这是jdk 1.6直线上升.

  • 它是在 JDK 5 中引入的(虽然没有人使用之前的版本所以没关系)https://docs.oracle.com/javase/8/docs/technotes/guides/collections/changes5.html (5认同)

Vit*_*nko 86

在Java 8中:

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

  • 为什么这比'Collections.frequency()`更好?它似乎不太可读. (8认同)
  • 使用Function.identity()(使用静态导入)而不是e - > e使它更好看. (6认同)
  • 这可能比所要求的更多,但它确实完全符合我的要求(将列表中的不同元素映射到他们的计数中).此外,这个问题是Google搜索时的最高结果. (5认同)
  • @rozina 您一次即可获得所有计数。 (2认同)

Osc*_*Ryz 22

这表明,为什么按照Effective Java book中的描述"按接口引用对象 "很重要.

如果您编写实现代码并使用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" ));
    }
}
Run Code Online (Sandbox Code Playgroud)

这里应用的OO原则:继承,多态,抽象,封装.

  • 那么一个人应该总是尝试组合而不是继承.当有时你想要LinkedList或其他时,你的实现现在停留在ArrayList.你的例子应该在它的构造函数/工厂中使用另一个LIst并返回一个包装器. (12认同)
  • 但是通过命名它CountItemsList你暗示它做两件事,它计算项目,它是一个列表.我认为只计算该类的一个单一责任,计算出现次数,将非常简单,您不需要实现List接口. (2认同)

dre*_*ash 12

为了实现这一目标,可以通过多种方式来实现,即:

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

收集频率

Collections.frequency(animals, "bat");
Run Code Online (Sandbox Code Playgroud)

Java 流:

筛选

animals.stream().filter("bat"::equals).count();
Run Code Online (Sandbox Code Playgroud)

只是迭代认为列表

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

创建频率图的方法:

Collectors.groupingBy

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

合并

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

手动

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

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

方法名称应该反映这些方法正在做什么,但是,我使用该名称来反映所使用的方法(考虑到在当前上下文中这是可以的)。


Ray*_*yat 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); }
}
Run Code Online (Sandbox Code Playgroud)

  • @dehmann,我不认为他真的想要 4 元素集合中蝙蝠出现的次数,我认为这只是样本数据,所以我们会更好地理解:-)。 (3认同)
  • 这真的不是一个可扩展的解决方案——想象一下 MM 的数据集有成百上千的条目,而 MM 想知道每个条目的频率。这可能是一项非常昂贵的任务——尤其是当有更好的方法来完成时。 (2认同)
  • @Vinegar 2/2.编程是关于现在正确处理的事情,所以我们不会给其他人带来麻烦或糟糕的体验,无论是未来的用户还是其他编码人员.PS:你写的代码越多,出现问题的可能性就越大. (2认同)
  • @mP:请解释为什么这不是一个可扩展的解决方案.Ray Hidayat正在为每个令牌构建频率计数,以便可以查找每个令牌.什么是更好的解决方案? (2认同)

Kev*_*vin 10

Java中没有本地方法可以帮助您.但是,您可以使用Apache Commons-Collections中的IterableUtils #countMatches()来为您完成.


Kha*_*aga 9

实际上,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"));
Run Code Online (Sandbox Code Playgroud)

  • Lars Andren在你的前5年发布了相同的答案. (25认同)

Ade*_*ari 8

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

我错了吗?

  • 通过"升级到1.6",它们可能意味着"升级以利用1.6中的新功能",而不是"修复与1.6的兼容性". (2认同)

Cri*_*ina 7

使用Streams的备用Java 8解决方案:

long count = animals.stream().filter(animal -> "bat".equals(animal)).count();
Run Code Online (Sandbox Code Playgroud)


Pet*_*rey 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();
}
Run Code Online (Sandbox Code Playgroud)


atr*_*atr 6

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

int noOfOccurs = Collections.frequency(animals, "bat");
Run Code Online (Sandbox Code Playgroud)

要在列表中获取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;
}
Run Code Online (Sandbox Code Playgroud)

将Collections.frequency称为:

int noOfOccurs = Collections.frequency(animals, new Animals(1));
Run Code Online (Sandbox Code Playgroud)


小智 6

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");
Run Code Online (Sandbox Code Playgroud)

方法一:

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

for (String s : set) {

    System.out.println(s + " : " + Collections.frequency(list, s));
}
Run Code Online (Sandbox Code Playgroud)

方法二:

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);
Run Code Online (Sandbox Code Playgroud)


Esw*_*san 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);
    }
Run Code Online (Sandbox Code Playgroud)

输出:{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()));
Run Code Online (Sandbox Code Playgroud)

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


mP.*_*mP. 5

你想要的是一个袋子 - 它就像一个套装,但也计算出现次数.不幸的是java Collections框架 - 很棒,因为他们没有袋子impl.为此,必须使用Apache Common Collection 链接文本

  • 最好的可扩展解决方案,如果你不能使用第三方的东西,就自己写。袋子不是创造火箭科学的。+1。 (2认同)