Como contar o número de ocorrências de um elemento em uma lista

173

Eu tenho uma ArrayListclasse Collection de Java, da seguinte maneira:

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

Como você pode ver, o animals ArrayListconsiste em 3 batelementos e um owlelemento. Gostaria de saber se existe alguma API na estrutura Collection que retorne o número de batocorrências ou se existe outra maneira de determinar o número de ocorrências.

Descobri que a coleção do Google Multisettem uma API que retorna o número total de ocorrências de um elemento. Mas isso é compatível apenas com o JDK 1.5. Atualmente, nosso produto está no JDK 1.6, então não posso usá-lo.

MILÍMETROS.
fonte
Essa é uma das razões pelas quais você deve programar para uma interface e não para uma implementação. Se você encontrar a coleção certa, precisará alterar o tipo para usá-la. Vou postar uma resposta sobre isso.
OscarRyz 03/02/09

Respostas:

333

Tenho certeza de que o método de frequência estática em Coleções seria útil aqui:

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

É assim que eu faria de qualquer maneira. Tenho certeza de que este é o jdk 1.6.

Lars Andren
fonte
Sempre prefira a API do JRE, que adiciona outra dependência ao projeto. E não reinventando a roda !!
Fernando.
Foi introduzido no JDK 5 (embora ninguém use uma versão anterior para que isso não importe) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim
105

No Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Vitalii Fedorenko
fonte
6
Usar Function.identity () (com importação estática) em vez de e -> e torna um pouco melhor de ler.
Kuchi
8
Por que isso é melhor do que Collections.frequency()? Parece menos legível.
rozina
Não foi isso que foi pedido. Faz mais trabalho do que o necessário.
quer
8
Isso pode fazer mais do que o solicitado, mas faz exatamente o que eu queria (obtenha um mapa de elementos distintos em uma lista para suas contagens). Além disso, essa pergunta foi o principal resultado no Google quando pesquisei.
KJP
@rozina Você recebe todas as contagens de uma só vez.
precisa saber é o seguinte
22

Isso mostra por que é importante " Consulte os objetos por suas interfaces ", conforme descrito no livro Java Efetivo .

Se você codifica para a implementação e usa ArrayList em, digamos, 50 lugares no seu código, quando você encontra uma boa implementação de "Lista" que conta os itens, precisará alterar todos esses 50 locais e provavelmente precisará quebre seu código (se ele for usado apenas por você, não há grande coisa, mas se for usado por outra pessoa, você também quebrará o código)

Ao programar para a interface, você pode deixar esses 50 locais inalterados e substituir a implementação de ArrayList para "CountItemsList" (por exemplo) ou alguma outra classe.

Abaixo está um exemplo muito básico de como isso pode ser escrito. Esta é apenas uma amostra, uma lista pronta para produção seria muito mais complicada.

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

Princípios OO aplicados aqui: herança, polimorfismo, abstração, encapsulamento.

OscarRyz
fonte
12
Bem, deve-se sempre tentar a composição, e não a herança. Sua implementação agora está presa ao ArrayList quando pode haver momentos em que você deseja um LinkedList ou outro. Seu exemplo deveria ter recebido outra lista no construtor / fábrica e retornado um invólucro.
mP.
Eu concordo completamente com você. O motivo pelo qual usei herança na amostra é porque é muito mais fácil mostrar um exemplo em execução usando herança do que composição (tendo que implementar a interface List). A herança cria o maior acoplamento.
OscarRyz 5/02/09
2
Mas, nomeando-o CountItemsList, significa que ele faz duas coisas, conta itens e é uma lista. Eu acho que apenas uma única responsabilidade por essa classe, contando as ocorrências, seria tão simples e você não precisaria implementar a interface List.
flob
11

Desculpe, não há uma chamada de método simples que possa fazer isso. Tudo o que você precisa fazer é criar um mapa e contar a frequência com ele.

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); }
}
Ray Hidayat
fonte
Esta não é realmente uma solução escalável - imagine que o conjunto de dados da MM possuísse centenas e milhares de entradas e a MM quisesse saber as frequências de cada entrada. Isso pode ser uma tarefa muito cara - especialmente quando existem maneiras muito melhores de fazê-lo.
mP.
Sim, pode não ser uma boa solução, não significa que está errado.
10249 Adeel Ansari
1
@ Dehmann, eu não acho que ele literalmente quer o número de ocorrências de morcegos em uma coleção de 4 elementos, acho que isso foi apenas uma amostra de dados para que possamos entender melhor :-).
314
2
@Vinegar 2/2. A programação é sobre fazer as coisas corretamente agora, por isso não causamos dores de cabeça ou uma experiência ruim para outra pessoa, seja um usuário ou outro codificador no futuro. PS: Quanto mais código você escrever, maior a chance de que algo possa dar errado.
mP.
2
@mP: Explique por que essa não é uma solução escalável. Ray Hidayat está criando uma contagem de frequências para cada token, para que cada token possa ser procurado. Qual é a melhor solução?
stackoverflowuser2010
10

Não há método nativo em Java para fazer isso por você. No entanto, você pode usar IterableUtils # countMatches () do Apache Commons-Collections para fazer isso por você.

Kevin
fonte
Consulte minha resposta abaixo - a resposta correta é usar uma estrutura que ofereça suporte à idéia de contagem desde o início, em vez de contar entradas do início ao fim toda vez que uma consulta é feita.
mP.
@mP Então você acabou de votar em todos que têm uma opinião diferente da sua? E se ele não puder usar uma bolsa por algum motivo ou estiver preso em usar uma das coleções nativas?
Kevin
-1 por ser um perdedor dolorido :-) Acho que o mP lhe deu um voto negativo porque sua solução custa tempo toda vez que você deseja um resultado. Uma bolsa custa um pouco de tempo apenas na inserção. Como os bancos de dados, esse tipo de estrutura tende a ser "mais lido do que escrito", por isso faz sentido usar a opção de baixo custo.
21420
E parece que sua resposta também requer coisas não-nativas, então seu comentário parece um pouco estranho.
21420
Graças a vocês dois. Acredito que uma das duas abordagens ou ambas possam funcionar. Vou tentar amanhã.
MM.
9

Na verdade, a classe Collections possui um método estático chamado: frequency (Collection c, Object o) que retorna o número de ocorrências do elemento que você está procurando, a propósito, isso funcionará perfeitamente para você:

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"));
Khafaga
fonte
27
Lars Andren postou a mesma resposta 5 anos antes da sua.
Fabian Barney
9

Solução alternativa para Java 8 usando Streams :

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

Gostaria de saber por que você não pode usar a API Collection do Google com o JDK 1.6. Diz isso? Eu acho que você pode, não deve haver problemas de compatibilidade, pois ele foi desenvolvido para uma versão inferior. O caso teria sido diferente se isso fosse construído para a 1.6 e você estivesse executando a 1.5.

Estou errado em algum lugar?

Adeel Ansari
fonte
Eles mencionaram claramente que estão atualizando sua API para o jdk 1.6.
MM.
1
Isso não torna o velho incompatível. Faz isso?
10249 Adeel Ansari
Não deveria. Mas a maneira como lançavam isenções de responsabilidade me deixa desconfortável em usá-lo na versão 0.9
MM.
Nós o usamos com o 1.6. Onde ele diz que é compatível apenas com 1.5?
284 Patrick Patrick
2
Ao "atualizar para a versão 1.6", eles provavelmente significam "atualizar para aproveitar as novidades da versão 1.6", "não" corrigindo a compatibilidade com a versão 1.6 ".
236 Adam Adamkiewicz
6

Uma abordagem um pouco mais eficiente pode ser

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();
}
Peter Lawrey
fonte
6

Para obter diretamente as ocorrências do objeto da lista:

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

Para obter a ocorrência da coleção Object dentro da lista, substitua o método equals na classe Object como:

@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;
}

Chame o Collections.frequency como:

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

Maneira simples de encontrar a ocorrência do valor da sequência em uma matriz usando os recursos do 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);
    }

Saída: {Gato = 2, Cabra = 1, Vaca = 1, Vaca = 1, Cão = 1}

Você pode notar que "vaca" e vaca não são consideradas a mesma sequência de caracteres, caso seja necessário na mesma contagem, use .toLowerCase (). Encontre o trecho abaixo para o mesmo.

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

Saída: {gato = 2, vaca = 2, cabra = 1, cachorro = 1}

Eswaran Venkatesan
fonte
nit: como a lista é uma lista de strings, não toString()é necessária. Você pode fazer:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad
5

O que você quer é uma bolsa - que é como um conjunto, mas também conta o número de ocorrências. Infelizmente, o framework java Collections - por mais que eles não tenham um implemento Bag. Para isso, é necessário usar o texto do link Apache Common Collection

mP.
fonte
1
A melhor solução escalável e, se você não puder usar itens de terceiros, basta escrever sua própria. Bolsas não são ciência do foguete para criar. +1.
314
Votado por dar uma resposta vaga, enquanto outros forneceram implementações para estruturas de dados de contagem de frequência. A estrutura de dados do 'pacote' a que você vinculou também não é uma solução apropriada para a pergunta do OP; essa estrutura de 'bolsa' destina-se a conter um número específico de cópias de um token, não a contar o número de ocorrências de tokens.
stackoverflowuser2010
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");

Método 1:

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

for (String s : set) {

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

Método 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);
sabm
fonte
Bem-vindo ao Stack Overflow! Considere explicar seu código para facilitar o entendimento de sua solução por outras pessoas.
Antimony
2

Se você usar o Eclipse Collections , poderá usar a Bag. A MutableBagpode ser retornado de qualquer implementação de RichIterable, chamando 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"));

A HashBagimplementação no Eclipse Collections é apoiada por a MutableObjectIntMap.

Nota: Sou um colaborador das Coleções Eclipse.

Donald Raab
fonte
1

Coloque os elementos da lista de matrizes no hashMap para contar a frequência.

Shamik
fonte
É exatamente a mesma coisa que o tweakt diz com um exemplo de código.
mP.
1

Java 8 - outro método

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

Faça da maneira antiquada e faça o seu próprio:

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++);
}
Mark Renouf
fonte
Com o "sincronizado" apropriado, se necessário, para evitar condições de corrida. Mas eu ainda prefiro ver isso em sua própria classe.
314
Você tem um erro de digitação. Em vez disso, você precisa do HashMap, como está no Map. Mas o erro de colocar 0 em vez de 1 é um pouco mais sério.
1130 Adeel Ansari
0

Se você é usuário do meu DSL ForEach , isso pode ser feito com uma Countconsulta.

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

Eu não queria tornar esse caso mais difícil e o tornei com dois iteradores. Eu tenho um HashMap com Sobrenome -> Nome. E meu método deve excluir itens com dulicate FirstName.

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

    }

}
Alexander Shapkin
fonte
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);

Resultado:

=mp= {Ram=2, Boss=1, Shiv=1}
Ramling Muley
fonte
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");
}
fcm45
fonte
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);
    }
}

Saída: 4

MD EMRUL EMRAN
fonte
É uma boa prática no Stack Overflow adicionar uma explicação sobre por que sua solução deve funcionar ou é melhor que as soluções existentes. Para mais informações, leia Como responder .
Samuel Liew