So zählen Sie die Anzahl der Vorkommen eines Elements in einer Liste

171

Ich habe eine ArrayList, eine Collection-Klasse von Java, wie folgt:

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

Wie Sie sehen können, animals ArrayListbesteht das aus 3 batElementen und einem owlElement. Ich habe mich gefragt, ob es im Collection-Framework eine API gibt, die die Anzahl der batVorkommen zurückgibt, oder ob es eine andere Möglichkeit gibt, die Anzahl der Vorkommen zu bestimmen.

Ich habe festgestellt, dass die Google-Sammlung Multisetüber eine API verfügt, die die Gesamtzahl der Vorkommen eines Elements zurückgibt. Das ist aber nur mit JDK 1.5 kompatibel. Unser Produkt befindet sich derzeit in JDK 1.6, daher kann ich es nicht verwenden.

MM.
quelle
Dies ist einer der Gründe, warum Sie eher auf eine Schnittstelle als auf eine Implementierung programmieren sollten. Wenn Sie die richtige Sammlung finden, müssen Sie den Typ ändern, um diese Sammlung zu verwenden. Ich werde eine Antwort darauf posten.
OscarRyz

Antworten:

331

Ich bin mir ziemlich sicher, dass die statische Frequenzmethode in Sammlungen hier nützlich sein würde:

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

So würde ich es sowieso machen. Ich bin mir ziemlich sicher, dass dies JDK 1.6 ist.

Lars Andren
quelle
Bevorzugen Sie immer Api von JRE, die dem Projekt eine weitere Abhängigkeit hinzufügen. Und das Rad nicht neu erfinden !!
Fernando.
Es wurde in JDK 5 eingeführt (obwohl vorher niemand eine Version verwendet, es spielt also keine Rolle) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim
104

In Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Vitalii Fedorenko
quelle
6
Die Verwendung von Function.identity () (mit statischem Import) anstelle von e -> e macht das Lesen etwas angenehmer.
Kuchi
8
Warum ist das besser als Collections.frequency()? Es scheint weniger lesbar.
Rozina
Dies ist nicht das, wonach gefragt wurde. Es macht mehr Arbeit als nötig.
Alex Worden
8
Dies kann mehr als das tun, wonach gefragt wurde, aber es macht genau das, was ich wollte (eine Karte mit verschiedenen Elementen in einer Liste zu ihren Zählungen erstellen). Darüber hinaus war diese Frage das Top-Ergebnis bei Google, als ich suchte.
KJP
@rozina Sie erhalten alle Zählungen in einem Durchgang.
AtoMerz
22

Dies zeigt, warum es wichtig ist, " Objekte über ihre Schnittstellen zu referenzieren ", wie im Buch " Effektives Java " beschrieben .

Wenn Sie für die Implementierung codieren und ArrayList an beispielsweise 50 Stellen in Ihrem Code verwenden und eine gute "List" -Implementierung finden, die die Elemente zählt, müssen Sie alle diese 50 Stellen ändern, und wahrscheinlich müssen Sie dies tun Brechen Sie Ihren Code (wenn er nur von Ihnen verwendet wird, gibt es keine große Sache, aber wenn er von jemand anderem verwendet wird, brechen Sie auch dessen Code)

Durch Programmieren auf die Schnittstelle können Sie diese 50 Stellen unverändert lassen und die Implementierung von ArrayList in "CountItemsList" (zum Beispiel) oder eine andere Klasse ersetzen.

Im Folgenden finden Sie ein sehr einfaches Beispiel dafür, wie dies geschrieben werden könnte. Dies ist nur ein Beispiel, eine produktionsreife Liste wäre viel komplizierter.

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

Hier angewandte OO-Prinzipien: Vererbung, Polymorphismus, Abstraktion, Kapselung.

OscarRyz
quelle
12
Nun, man sollte immer eher Komposition als Vererbung versuchen. Ihre Implementierung bleibt jetzt bei ArrayList hängen, wenn Sie manchmal eine LinkedList oder eine andere möchten. Ihr Beispiel sollte eine andere LIst in ihrem Konstruktor / seiner Fabrik genommen und einen Wrapper zurückgegeben haben.
mP.
Ich stimme völlig mit Ihnen. Der Grund, warum ich im Beispiel die Vererbung verwendet habe, ist, dass es viel einfacher ist, ein laufendes Beispiel mit Vererbung zu zeigen als die Komposition (die List-Schnittstelle muss implementiert werden). Vererbung schafft die höchste Kopplung.
OscarRyz
2
Wenn Sie es jedoch CountItemsList nennen, implizieren Sie, dass es zwei Dinge tut, Elemente zählt und eine Liste ist. Ich denke, nur eine einzige Verantwortung für diese Klasse, das Zählen der Vorkommen, wäre so einfach und Sie müssten die List-Schnittstelle nicht implementieren.
Flob
11

Leider gibt es keinen einfachen Methodenaufruf, der dies ermöglicht. Alles, was Sie tun müssen, ist eine Karte zu erstellen und die Häufigkeit damit zu zählen.

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
quelle
Dies ist wirklich keine skalierbare Lösung - stellen Sie sich vor, der Datensatz von MM hatte Hunderte und Tausende von Einträgen, und MM wollte die Frequenzen für jeden einzelnen Eintrag wissen. Dies könnte möglicherweise eine sehr kostspielige Aufgabe sein - insbesondere wenn es viel bessere Möglichkeiten gibt, dies zu tun.
mP.
Ja, es ist möglicherweise keine gute Lösung, bedeutet aber nicht, dass es falsch ist.
Adeel Ansari
1
@dehmann, ich glaube nicht, dass er buchstäblich die Anzahl der Fledermausvorkommen in einer 4-Elemente-Sammlung haben möchte. Ich denke, das waren nur Beispieldaten, damit wir es besser verstehen :-).
Paxdiablo
2
@ Vinegar 2/2. Beim Programmieren geht es darum, die Dinge jetzt richtig zu machen, damit wir in Zukunft keine Kopfschmerzen oder schlechte Erfahrungen für andere Personen verursachen, sei es für einen Benutzer oder einen anderen Programmierer. PS: Je mehr Code Sie schreiben, desto größer ist die Wahrscheinlichkeit, dass etwas schief gehen kann.
mP.
2
@mP: Bitte erklären Sie, warum dies keine skalierbare Lösung ist. Ray Hidayat erstellt für jedes Token eine Frequenzzählung, damit jedes Token nachgeschlagen werden kann. Was ist eine bessere Lösung?
stackoverflowuser2010
10

In Java gibt es keine native Methode, um dies für Sie zu tun. Sie können jedoch IterableUtils # countMatches () aus Apache Commons-Collections verwenden, um dies für Sie zu tun.

Kevin
quelle
Siehe meine Antwort unten - die richtige Antwort besteht darin, eine Struktur zu verwenden, die die Zählidee von Anfang an unterstützt, anstatt die Einträge bei jeder Abfrage von Anfang bis Ende zu zählen.
mP.
@mP Also hast du einfach alle abgelehnt, die eine andere Meinung haben als du? Was ist, wenn er aus irgendeinem Grund keine Tasche benutzen kann oder nicht in der Lage ist, eine der einheimischen Sammlungen zu benutzen?
Kevin
-1, weil du ein wunder Verlierer bist :-) Ich denke, mP hat dich herabgestimmt, weil deine Lösung jedes Mal Zeit kostet, wenn du ein Ergebnis willst. Eine Tasche kostet nur beim Einsetzen etwas Zeit. Wie bei Datenbanken sind diese Strukturen in der Regel "mehr lesen als schreiben", daher ist es sinnvoll, die kostengünstige Option zu verwenden.
Paxdiablo
Und es scheint, dass Ihre Antwort auch nicht-native Inhalte erfordert, sodass Ihr Kommentar etwas seltsam erscheint.
Paxdiablo
Vielen Dank an euch beide. Ich glaube, einer der beiden Ansätze oder beide könnten funktionieren. Ich werde es morgen versuchen.
MM.
9

Tatsächlich verfügt die Collections-Klasse über eine statische Methode namens: Frequency (Collection c, Object o), die die Anzahl der Vorkommen des gesuchten Elements zurückgibt. Dies funktioniert übrigens perfekt für Sie:

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
quelle
27
Lars Andren hat die gleiche Antwort 5 Jahre vor Ihrer gepostet.
Fabian Barney
8

Ich frage mich, warum Sie die Google Collection-API nicht mit JDK 1.6 verwenden können. Sagt es so? Ich denke, Sie können, es sollte keine Kompatibilitätsprobleme geben, da es für eine niedrigere Version gebaut wurde. Der Fall wäre anders gewesen, wenn das für 1.6 erstellt worden wäre und Sie 1.5 ausführen.

Liege ich irgendwo falsch

Adeel Ansari
quelle
Sie haben klar erwähnt, dass sie gerade dabei sind, ihre API auf JDK 1.6 zu aktualisieren.
MM.
1
Das macht alt nicht inkompatibel. Macht es?
Adeel Ansari
Es sollte nicht. Aber die Art und Weise, wie sie Haftungsausschlüsse geworfen haben, macht es mir unangenehm, sie in ihrer 0.9-Version
MM
Wir verwenden es mit 1.6. Wo steht, dass es nur mit 1.5 kompatibel ist?
Patrick
2
Mit "Upgrade auf 1.6" meinen sie wahrscheinlich "Upgrade, um neue Funktionen in 1.6 zu nutzen", nicht "Kompatibilität mit 1.6 reparieren".
Adam Jaskiewicz
8

Alternative Java 8- Lösung mit Streams :

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

Ein etwas effizienterer Ansatz könnte sein

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
quelle
6

So rufen Sie die Vorkommen des Objekts direkt aus der Liste ab:

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

Überschreiben Sie die equals-Methode in der Object-Klasse wie folgt, um das Auftreten der Object-Auflistung in der Liste zu ermitteln:

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

Rufen Sie die Collections.frequency auf als:

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

Einfache Methode zum Ermitteln des Auftretens von Zeichenfolgenwerten in einem Array mithilfe von Java 8-Funktionen.

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

Ausgabe: {Katze = 2, Ziege = 1, Kuh = 1, Kuh = 1, Hund = 1}

Sie können feststellen, dass "Cow" und cow nicht als dieselbe Zeichenfolge betrachtet werden. Verwenden Sie .toLowerCase (), falls Sie dies unter derselben Anzahl erforderlich gemacht haben. Bitte finden Sie das Snippet unten für das gleiche.

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

Ausgabe: {Katze = 2, Kuh = 2, Ziege = 1, Hund = 1}

Eswaran Venkatesan
quelle
nit: Da die Liste eine Liste von Zeichenfolgen ist, toString()ist sie nicht erforderlich. Sie können einfach tun:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad
5

Was Sie wollen, ist eine Tasche - die wie ein Set ist, aber auch die Anzahl der Vorkommen zählt. Leider ist das Java Collections Framework - großartig, da sie kein Bag Impl haben. Dazu muss der Linktext Apache Common Collection verwendet werden

mP.
quelle
1
Beste skalierbare Lösung. Wenn Sie keine Inhalte von Drittanbietern verwenden können, schreiben Sie einfach Ihre eigenen. Taschen sind keine Raketenwissenschaft. +1.
Paxdiablo
Für eine vage Antwort abgelehnt, während andere Implementierungen für Frequenzzähldatenstrukturen bereitgestellt haben. Die von Ihnen verknüpfte 'bag'-Datenstruktur ist auch keine geeignete Lösung für die Frage des OP. Diese "Taschen" -Struktur soll eine bestimmte Anzahl von Kopien eines Tokens enthalten, nicht die Anzahl der Vorkommen von 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");

Methode 1:

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

for (String s : set) {

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

Methode 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
quelle
Willkommen bei Stack Overflow! Erklären Sie Ihren Code, um anderen das Verständnis Ihrer Lösung zu erleichtern.
Antimon
2

Wenn Sie Eclipse-Sammlungen verwenden , können Sie a verwenden Bag. A MutableBagkann von jeder Implementierung von RichIterableper Aufruf zurückgegeben werden 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"));

Die HashBagImplementierung in Eclipse Collections wird durch a unterstützt MutableObjectIntMap.

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.

Donald Raab
quelle
1

Fügen Sie die Elemente der Arrayliste in die HashMap ein, um die Häufigkeit zu zählen.

Shamik
quelle
Dies ist genau das gleiche, was tweakt mit einem Codebeispiel sagt.
mP.
1

Java 8 - eine andere Methode

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

Also mach es auf die altmodische Art und roll deine eigene:

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
quelle
Bei Bedarf mit dem entsprechenden "synchronisiert", um Rennbedingungen zu vermeiden. Aber ich würde es immer noch vorziehen, dies in seiner eigenen Klasse zu sehen.
Paxdiablo
Du hast einen Tippfehler. Benötigen Sie stattdessen HashMap, da Sie es in Map aufnehmen. Der Fehler, 0 anstelle von 1 zu setzen, ist jedoch etwas schwerwiegender.
Adeel Ansari
0

Wenn Sie ein Benutzer von ForEach DSL sind , kann dies mit einer CountAbfrage erfolgen.

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

Ich wollte diesen Fall nicht schwieriger machen und machte es mit zwei Iteratoren. Ich habe eine HashMap mit Nachname -> Vorname. Und meine Methode sollte Elemente mit dem doppelten Vornamen löschen.

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

Ausgabe:

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

Ausgabe: 4

MD EMRUL EMRAN
quelle
Bei Stack Overflow empfiehlt es sich, eine Erklärung hinzuzufügen, warum Ihre Lösung funktionieren sollte oder besser ist als die vorhandenen Lösungen. Weitere Informationen finden Sie unter Beantworten .
Samuel Liew