Comment compter le nombre d'occurrences d'un élément dans une liste

173

J'ai une ArrayList, une classe Collection de Java, comme suit:

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

Comme vous pouvez le voir, le se animals ArrayListcompose de 3 batéléments et d'un owlélément. Je me demandais s'il existe une API dans le framework Collection qui renvoie le nombre d' batoccurrences ou s'il existe un autre moyen de déterminer le nombre d'occurrences.

J'ai trouvé que Google's Collection Multiseta une API qui renvoie le nombre total d'occurrences d'un élément. Mais cela n'est compatible qu'avec JDK 1.5. Notre produit est actuellement en JDK 1.6, donc je ne peux pas l'utiliser.

MM.
la source
C'est l'une des raisons pour lesquelles vous devez programmer une interface plutôt qu'une implémentation. Si vous trouvez la bonne collection, vous devrez changer le type pour utiliser cette collection. Je posterai une réponse à ce sujet.
OscarRyz

Réponses:

333

Je suis presque sûr que la méthode de fréquence statique dans les collections serait utile ici:

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

C'est comme ça que je le ferais de toute façon. Je suis presque sûr que c'est jdk 1.6 directement.

Lars Andren
la source
Préférez toujours Api de JRE, qui ajoute une autre dépendance au projet. Et ne réinventez pas la roue !!
Fernando.
Il a été introduit dans JDK 5 (bien que personne n'utilise une version avant cela, donc peu importe) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim
105

Dans Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Vitalii Fedorenko
la source
6
Utiliser Function.identity () (avec import statique) au lieu de e -> e le rend un peu plus agréable à lire.
Kuchi
8
Pourquoi est-ce mieux que Collections.frequency()? Cela semble moins lisible.
rozina
Ce n'est pas ce qui a été demandé. Cela fait plus de travail que nécessaire.
Alex Worden
8
Cela peut faire plus que ce qui a été demandé, mais cela fait exactement ce que je voulais (obtenir une carte des éléments distincts dans une liste à leurs décomptes). De plus, cette question était le meilleur résultat dans Google lorsque j'ai cherché.
KJP
@rozina Vous obtenez tous les comptes en un seul passage.
atoMerz
22

Cela montre pourquoi il est important de «faire référence aux objets par leurs interfaces » comme décrit dans le livre Effective Java .

Si vous codez pour l'implémentation et utilisez ArrayList dans, disons, 50 endroits dans votre code, lorsque vous trouvez une bonne implémentation "List" qui compte les éléments, vous devrez changer tous ces 50 emplacements, et vous devrez probablement casser votre code (s'il n'est utilisé que par vous, il n'y a pas grand-chose, mais s'il est utilisé par quelqu'un d'autre, vous casserez aussi son code)

En programmant sur l'interface, vous pouvez laisser ces 50 emplacements inchangés et remplacer l'implémentation d'ArrayList par «CountItemsList» (par exemple) ou une autre classe.

Vous trouverez ci-dessous un exemple très basique sur la façon dont cela pourrait être écrit. Ceci n'est qu'un exemple, une liste prête pour la production serait beaucoup plus compliquée.

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

Principes OO appliqués ici: héritage, polymorphisme, abstraction, encapsulation.

OscarRyz
la source
12
Eh bien, il faut toujours essayer la composition plutôt que l'héritage. Votre implémentation est maintenant bloquée sur ArrayList lorsque vous souhaiterez peut-être une LinkedList ou autre. Votre exemple aurait dû prendre un autre LIst dans son constructeur / usine et renvoyer un wrapper.
mP.
Je suis complètement d'accord avec toi. La raison pour laquelle j'ai utilisé l'héritage dans l'exemple est qu'il est beaucoup plus facile de montrer un exemple en cours utilisant l'héritage que la composition (devoir implémenter l'interface de liste). L'héritage crée le couplage le plus élevé.
OscarRyz
2
Mais en le nommant CountItemsList, vous impliquez qu'il fait deux choses, il compte les éléments et c'est une liste. Je pense qu'une seule responsabilité pour cette classe, en comptant les occurrences, serait aussi simple et vous n'auriez pas besoin d'implémenter l'interface List.
flob
11

Désolé, aucun appel de méthode simple ne peut le faire. Tout ce que vous avez à faire est de créer une carte et de compter la fréquence avec.

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
la source
Ce n'est vraiment pas une solution évolutive - imaginez que l'ensemble de données de MM contenait des centaines et des milliers d'entrées et que MM voulait connaître les fréquences pour chaque entrée. Cela pourrait potentiellement être une tâche très coûteuse, en particulier lorsqu'il existe de bien meilleures façons de le faire.
mP.
Oui, ce n'est peut-être pas une bonne solution, cela ne veut pas dire que c'est faux.
Adeel Ansari
1
@dehmann, je ne pense pas qu'il veuille littéralement le nombre d'occurrences de chauves-souris dans une collection de 4 éléments, je pense que c'était juste des exemples de données pour que nous comprenions mieux :-).
paxdiablo
2
@Vinegar 2/2. La programmation consiste à faire les choses correctement maintenant, donc nous ne causons pas de maux de tête ou une mauvaise expérience à quelqu'un d'autre que ce soit un utilisateur ou un autre codeur à l'avenir. PS: Plus vous écrivez de code, plus il y a de chances que quelque chose se passe mal.
mP.
2
@mP: Veuillez expliquer pourquoi ce n'est pas une solution évolutive. Ray Hidayat construit un décompte de fréquences pour chaque jeton afin que chaque jeton puisse ensuite être recherché. Quelle est la meilleure solution?
stackoverflowuser2010
10

Il n'y a pas de méthode native en Java pour le faire pour vous. Cependant, vous pouvez utiliser IterableUtils # countMatches () d'Apache Commons-Collections pour le faire pour vous.

Kevin
la source
Reportez-vous à ma réponse ci-dessous - la bonne réponse est d'utiliser une structure qui prend en charge l'idée de comptage depuis le début plutôt que de compter les entrées du début à la fin chaque fois qu'une requête est effectuée.
mP.
@mP Vous venez de voter contre tous ceux qui ont une opinion différente de vous? Que faire s'il ne peut pas utiliser un sac pour une raison quelconque ou s'il ne peut pas utiliser l'une des collections natives?
Kevin
-1 pour avoir été un mauvais perdant :-) Je pense que mP vous a déconseillé parce que votre solution coûte du temps à chaque fois que vous voulez un résultat. Un sac coûte un peu de temps seulement à l'insertion. Comme les bases de données, ces types de structures ont tendance à être «plus en lecture qu'en écriture», il est donc logique d'utiliser l'option à faible coût.
paxdiablo
Et il semble que votre réponse nécessite également des éléments non natifs, donc votre commentaire semble un peu étrange.
paxdiablo
Merci à vous deux. Je pense que l’une des deux approches ou les deux pourraient fonctionner. Je vais essayer demain.
MM.
9

En fait, la classe Collections a une méthode statique appelée: frequency (Collection c, Object o) qui renvoie le nombre d'occurrences de l'élément que vous recherchez, au fait, cela fonctionnera parfaitement pour vous:

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
la source
27
Lars Andren a publié la même réponse 5 ans avant la vôtre.
Fabian Barney
9

Solution alternative Java 8 utilisant Streams :

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

Je me demande pourquoi vous ne pouvez pas utiliser l'API de collecte de Google avec JDK 1.6. Le dit-il? Je pense que vous pouvez, il ne devrait y avoir aucun problème de compatibilité, car il est conçu pour une version inférieure. Le cas aurait été différent si cela avait été construit pour 1.6 et que vous exécutiez 1.5.

Est-ce que je me trompe quelque part?

Adeel Ansari
la source
Ils ont clairement mentionné qu'ils sont en train de mettre à niveau leur API vers jdk 1.6.
MM.
1
Cela ne rend pas l'ancien incompatible. Le fait-il?
Adeel Ansari
Ça ne devrait pas. Mais la façon dont ils lançaient des clauses de non-responsabilité me rend mal à l'aise de l'utiliser dans leur version 0.9
MM.
Nous l'utilisons avec 1.6. Où est-il dit qu'il n'est compatible qu'avec la version 1.5?
Patrick
2
Par "mise à jour vers 1.6", ils signifient probablement "mise à niveau pour profiter des nouveautés de la 1.6", et non "fixation de la compatibilité avec 1.6".
Adam Jaskiewicz le
6

Une approche légèrement plus efficace pourrait être

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
la source
6

Pour obtenir directement les occurrences de l'objet dans la liste:

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

Pour obtenir l'occurrence de la collection Object dans la liste, remplacez la méthode equals dans la classe Object comme suit:

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

Appelez Collections.frequency comme suit:

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

Méthode simple pour trouver l'occurrence d'une valeur de chaîne dans un tableau à l'aide des fonctionnalités de 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);
    }

Sortie: {Chat = 2, Chèvre = 1, Vache = 1, Vache = 1, Chien = 1}

Vous pouvez remarquer que "Cow" et cow ne sont pas considérés comme la même chaîne, au cas où vous en auriez besoin sous le même nombre, utilisez .toLowerCase (). Veuillez trouver l'extrait ci-dessous pour le même.

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

Sortie: {chat = 2, vache = 2, chèvre = 1, chien = 1}

Eswaran Venkatesan
la source
nit: parce que la liste est une liste de chaînes, toString()est inutile. Vous pouvez simplement faire:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad
5

Ce que vous voulez, c'est un sac - qui est comme un ensemble mais qui compte également le nombre d'occurrences. Malheureusement, le framework java Collections - génial car ils n'ont pas d'implément de sac. Pour cela, il faut utiliser le texte du lien Apache Common Collection

mP.
la source
1
Meilleure solution évolutive et, si vous ne pouvez pas utiliser des éléments tiers, écrivez simplement les vôtres. Les sacs ne sont pas sorciers à créer. +1.
paxdiablo
Évalué pour avoir donné une réponse vague tandis que d'autres ont fourni des implémentations pour les structures de données de comptage de fréquence. La structure de données «sac» à laquelle vous avez lié n'est pas non plus une solution appropriée à la question du PO; cette structure «sac» est destinée à contenir un nombre spécifique de copies d'un jeton, et non à compter le nombre d'occurrences de jetons.
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éthode 1:

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

for (String s : set) {

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

Méthode 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
la source
Bienvenue dans Stack Overflow! Pensez à expliquer votre code pour permettre aux autres de comprendre plus facilement votre solution.
Antimoine
2

Si vous utilisez des collections Eclipse , vous pouvez utiliser un fichier Bag. A MutableBagpeut être renvoyé à partir de n'importe quelle implémentation de RichIterableen appelant 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' HashBagimplémentation dans les collections Eclipse est soutenue par un fichier MutableObjectIntMap.

Remarque: je suis un committer pour les collections Eclipse.

Donald Raab
la source
1

Mettez les éléments de l'arraylist dans le hashMap pour compter la fréquence.

Shamik
la source
C'est exactement la même chose que tweakt dit avec un exemple de code.
mP.
1

Java 8 - une autre méthode

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

Alors faites-le à l'ancienne et roulez le vôtre:

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
la source
Avec le "synchronisé" approprié, si nécessaire, pour éviter les conditions de course. Mais je préférerais quand même voir cela dans sa propre classe.
paxdiablo
Vous avez une faute de frappe. Vous avez besoin de HashMap à la place, car vous le prenez dans Map. Mais l'erreur de mettre 0 au lieu de 1 est un peu plus grave.
Adeel Ansari
0

Si vous êtes un utilisateur de mon ForEach DSL , cela peut être fait avec une Countrequête.

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

Je ne voulais pas rendre ce cas plus difficile et je l'ai fait avec deux itérateurs J'ai un HashMap avec LastName -> FirstName. Et ma méthode devrait supprimer les éléments avec un prénom dulicat.

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

Production:

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

Sortie: 4

MD EMRUL EMRAN
la source
C'est une bonne pratique sur Stack Overflow d'ajouter une explication sur les raisons pour lesquelles votre solution devrait fonctionner ou est meilleure que les solutions existantes. Pour plus d'informations, lisez Comment répondre .
Samuel Liew