Come contare il numero di occorrenze di un elemento in un elenco


173

Ho ArrayListuna classe Collection di Java come segue:

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

Come puoi vedere, è animals ArrayListcomposto da 3 batelementi e un owlelemento. Mi chiedevo se ci fosse qualche API nel framework Collection che restituisce il numero di batoccorrenze o se esiste un altro modo per determinare il numero di occorrenze.

Ho scoperto che la Collezione di Google Multisetha un'API che restituisce il numero totale di occorrenze di un elemento. Ma questo è compatibile solo con JDK 1.5. Il nostro prodotto è attualmente in JDK 1.6, quindi non posso usarlo.


Questo è uno dei motivi per cui dovresti programmare su un'interfaccia piuttosto che un'implementazione. Se ti capita di trovare la collezione giusta, dovrai cambiare il tipo per usare quella collezione. Invierò una risposta su questo.
OscarRyz,

Risposte:


333

Sono abbastanza sicuro che il metodo della frequenza statica nelle Collezioni sarebbe utile qui:

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

È così che lo farei comunque. Sono abbastanza sicuro che questo sia jdk 1.6 direttamente.


Preferisci sempre Api di JRE, che aggiunge un'altra dipendenza al progetto. E non reinventare la ruota !!
Fernando.

È stato introdotto in JDK 5 (anche se nessuno usa una versione precedente, quindi non importa) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim

105

In Java 8:

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

6
L'uso di Function.identity () (con importazione statica) invece di e -> e rende un po 'più piacevole la lettura.
Kuchi,

8
Perché è meglio di Collections.frequency()? Sembra meno leggibile.
rozina

Questo non è ciò che è stato chiesto. Fa più lavoro del necessario.
Alex Worden,

8
Questo può fare più di quello che mi è stato chiesto, ma fa esattamente quello che volevo (ottenere una mappa di elementi distinti in un elenco per i loro conteggi). Inoltre, questa domanda è stata il miglior risultato in Google quando ho cercato.
KJP

@rozina Ottieni tutti i conteggi in un unico passaggio.
atoMerz

22

Ciò mostra perché è importante " Fare riferimento agli oggetti tramite le loro interfacce " come descritto nel libro Java efficace .

Se esegui il codice per l'implementazione e usi ArrayList in diciamo, 50 posti nel tuo codice, quando trovi una buona implementazione "Elenco" che conta gli elementi, dovrai cambiare tutti quei 50 posti e probabilmente dovrai rompere il codice (se viene utilizzato solo da te, non c'è un grosso problema, ma se viene utilizzato da qualcun altro lo utilizza, anche il codice verrà infranto)

Programmando sull'interfaccia è possibile lasciare invariati quei 50 posti e sostituire l'implementazione da ArrayList a "CountItemsList" (ad esempio) o qualche altra classe.

Di seguito è riportato un esempio molto semplice su come potrebbe essere scritto. Questo è solo un esempio, un elenco pronto per la produzione sarebbe molto più complicato.

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

Principi OO applicati qui: eredità, polimorfismo, astrazione, incapsulamento.


12
Bene, si dovrebbe sempre provare la composizione piuttosto che l'eredità. L'implementazione è ora bloccata su ArrayList quando potrebbero esserci delle volte in cui desideri un LinkedList o altro. Il tuo esempio avrebbe dovuto prendere un altro LI nel suo costruttore / fabbrica e restituito un wrapper.
mP.

Sono completamente d'accordo con voi. Il motivo per cui ho usato l'ereditarietà nell'esempio è perché è molto più semplice mostrare un esempio in esecuzione usando l'ereditarietà rispetto alla composizione (è necessario implementare l'interfaccia Elenco). L'ereditarietà crea l'accoppiamento più elevato.
OscarRyz,

2
Ma nominandolo CountItemsList implica che fa due cose, conta gli elementi ed è un elenco. Penso che una sola responsabilità per quella classe, contando le occorrenze, sarebbe altrettanto semplice e non avresti bisogno di implementare l'interfaccia List.
flob

11

Spiacente, non esiste una semplice chiamata di metodo che può farlo. Tutto ciò che dovresti fare è creare una mappa e contare la frequenza con essa.

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

Questa non è davvero una soluzione scalabile - immagina che il set di dati di MM avesse centinaia e migliaia di voci e MM voleva conoscere le frequenze per ogni voce. Questo potrebbe potenzialmente essere un compito molto costoso, specialmente quando ci sono modi molto migliori per farlo.
mP.

Sì, potrebbe non essere una buona soluzione, non significa che sia sbagliato.
Adeel Ansari,

1
@dehmann, non credo che voglia letteralmente il numero di occorrenze di pipistrelli in una raccolta di 4 elementi, penso che fossero solo dati di esempio, quindi capiremmo meglio :-).
paxdiablo,

2
@Vinegar 2/2. La programmazione consiste nel fare le cose correttamente ora, quindi non causeremo mal di testa o una brutta esperienza per qualcun altro, sia esso un utente o un altro programmatore in futuro. PS: più codice scrivi, più possibilità c'è che qualcosa vada storto.
mP.

2
@mP: spiega perché questa non è una soluzione scalabile. Ray Hidayat sta costruendo un conteggio di frequenza per ogni token in modo che ogni token possa quindi essere cercato. Qual è una soluzione migliore?
stackoverflowuser2010

10

Non esiste un metodo nativo in Java per farlo. Tuttavia, puoi usare IterableUtils # countMatches () da Apache Commons-Collections per farlo per te.


Fai riferimento alla mia risposta di seguito: la risposta corretta è utilizzare una struttura che supporti l'idea di conteggio dall'inizio piuttosto che contare le voci dall'inizio alla fine ogni volta che viene effettuata una query.
mP.

@mP Quindi hai appena sottovalutato tutti quelli che hanno un'opinione diversa da te? E se non fosse in grado di utilizzare una borsa per qualche motivo o fosse bloccato con una delle collezioni native?
Kevin

-1 per essere un perdente dolorante :-) Penso che mP ti abbia retrocesso perché la tua soluzione costa tempo ogni volta che vuoi un risultato. Una borsa costa un po 'di tempo solo all'inserimento. Come i database, questo tipo di strutture tendono ad essere "più letti che scritti", quindi ha senso usare l'opzione a basso costo.
paxdiablo,

E sembra che la tua risposta richieda anche cose non native, quindi il tuo commento sembra un po 'strano.
paxdiablo,

Grazie a tutti e due ragazzi. Credo che uno dei due approcci o entrambi potrebbero funzionare. Ci proverò domani.
MM.

9

In realtà, la classe Collections ha un metodo statico chiamato: frequency (Collection c, Object o) che restituisce il numero di occorrenze dell'elemento che stai cercando, a proposito, questo funzionerà perfettamente per te:

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 ha pubblicato la stessa risposta 5 anni prima della tua.
Fabian Barney,

9

Soluzione alternativa Java 8 che utilizza Stream :

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

8

Mi chiedo, perché non è possibile utilizzare l'API Collection di Google con JDK 1.6. Lo dice? Penso che puoi, non dovrebbero esserci problemi di compatibilità, poiché è stato creato per una versione precedente. Il caso sarebbe stato diverso se fosse stato creato per 1.6 e si eseguisse 1.5.

Sbaglio da qualche parte?


Hanno chiaramente menzionato che stanno aggiornando il loro API a jdk 1.6.
MM.

1
Ciò non rende il vecchio incompatibile. Vero?
Adeel Ansari,

Non dovrebbe. Ma il modo in cui lanciavano dichiarazioni di non responsabilità, mi mette a disagio a usarlo nella loro versione 0.9
MM.

Lo usiamo con 1.6. Dove dice che è compatibile solo con 1.5?
Patrick,

2
Con "upgrade a 1.6" significano probabilmente "upgrade per sfruttare le novità in 1.6," non "correggere la compatibilità con 1.6".
Adam Jaskiewicz,

6

Potrebbe essere un approccio leggermente più efficiente

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

Per ottenere direttamente le occorrenze dell'oggetto dall'elenco:

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

Per ottenere la ricorrenza dell'elenco Object collection all'interno, sovrascrivere il metodo equals nella classe Object come:

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

Chiama la Collezioni.frequency come:

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

6

Modo semplice per trovare la ricorrenza del valore di stringa in un array utilizzando le funzionalità di 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);
    }

Uscita: {Gatto = 2, Capra = 1, Mucca = 1, mucca = 1, Cane = 1}

Puoi notare che "Cow" e cow non sono considerati come la stessa stringa, nel caso in cui lo richiedessi con lo stesso conteggio, usa .toLowerCase (). Trova lo snippet di seguito per lo stesso.

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

Uscita: {cat = 2, mucca = 2, capra = 1, cane = 1}


nit: poiché l'elenco è un elenco di stringhe, toString()è inutile. Puoi semplicemente fare:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad

5

Quello che vuoi è una borsa, che è come un set ma conta anche il numero di occorrenze. Sfortunatamente il framework Collezioni java - fantastico in quanto non ha un impl Bag. Per questo è necessario utilizzare il testo del collegamento Raccolta comune Apache


1
La migliore soluzione scalabile e, se non riesci a utilizzare roba di terze parti, basta scrivere la tua. Le borse non sono scienza missilistica da creare. +1.
paxdiablo,

Sottovalutato per aver dato una risposta vaga mentre altri hanno fornito implementazioni per strutture di dati per il conteggio delle frequenze. La struttura dei dati "bag" a cui si è collegati non è inoltre una soluzione adeguata alla domanda del PO; quella struttura "bag" è destinata a contenere un numero specifico di copie di un token, non a contare il numero di occorrenze di token.
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");

Metodo 1:

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

for (String s : set) {

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

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

Benvenuto in Stack Overflow! Valuta la possibilità di spiegare il tuo codice per rendere più semplice agli altri la comprensione della tua soluzione.
Antimonio,

2

Se si utilizzano le raccolte Eclipse , è possibile utilizzare a Bag. A MutableBagpuò essere restituito da qualsiasi implementazione di RichIterablechiamando 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"));

L' HashBagimplementazione in Eclipse Collections è supportata da a MutableObjectIntMap.

Nota: sono un committer per le raccolte Eclipse.


1

Inserisci gli elementi dell'arraylist nell'hashMap per contare la frequenza.


Questa è esattamente la stessa cosa che tweakt dice con un esempio di codice.
mP.

1

Java 8 - un altro metodo

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

0

Quindi fallo alla vecchia maniera e fai il tuo:

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

Con l'appropriato "sincronizzato", se necessario, per evitare condizioni di gara. Ma preferirei ancora vederlo nella sua classe.
paxdiablo,

Hai un refuso. Hai bisogno di HashMap invece, come lo stai prendendo in Mappa. Ma l'errore di mettere 0 invece di 1 è un po 'più grave.
Adeel Ansari,

0

Se sei un utente del mio DSL ForEach , puoi farlo con una Countquery.

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

0

Non volevo rendere questo caso più difficile e, con due iteratori, ho creato una HashMap con LastName -> FirstName. E il mio metodo dovrebbe eliminare gli elementi con 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();

    }

}

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

Produzione:

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

Uscita: 4


È buona norma su Stack Overflow aggiungere una spiegazione del perché la soluzione dovrebbe funzionare o è migliore delle soluzioni esistenti. Per maggiori informazioni leggi Come rispondere .
Samuel Liew
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.