Jak policzyć liczbę wystąpień elementu na liście

173

Mam następującą ArrayListklasę Collection języka Java:

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

Jak widać, animals ArrayListskłada się z 3 batelementów i jednego owlelementu. Zastanawiałem się, czy w ramach Collection istnieje interfejs API, który zwraca liczbę batwystąpień, czy też istnieje inny sposób określenia liczby wystąpień.

Odkryłem, że kolekcja Google Multisetma interfejs API, który zwraca całkowitą liczbę wystąpień elementu. Ale to jest kompatybilne tylko z JDK 1.5. Nasz produkt jest obecnie w JDK 1.6, więc nie mogę go używać.

MM.
źródło
To jeden z powodów, dla których powinieneś programować w interfejsie zamiast implementacji. Jeśli znajdziesz odpowiednią kolekcję, musisz zmienić typ, aby korzystać z tej kolekcji. Napiszę odpowiedź na ten temat.
OscarRyz,

Odpowiedzi:

333

Jestem prawie pewien, że metoda statycznej częstotliwości w Kolekcjach przydałaby się tutaj:

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

Tak i tak bym to zrobił. Jestem prawie pewien, że to jest jdk 1.6 prosto.

Lars Andren
źródło
Zawsze preferuj interfejs API od środowiska JRE, który dodaje kolejną zależność do projektu. I nie wymyślaj na nowo koła !!
Fernando.
Został wprowadzony w JDK 5 (chociaż nikt wcześniej nie używał wersji, więc nie ma to znaczenia) docs.oracle.com/javase/8/docs/technotes/guides/collections/ ...
Minion Jim
105

W Javie 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Vitalii Fedorenko
źródło
6
Użycie funkcji Function.identity () (ze statycznym importem) zamiast e -> e sprawia, że ​​czytanie jest trochę przyjemniejsze.
Kuchi
8
Dlaczego to jest lepsze niż Collections.frequency()? Wydaje się mniej czytelne.
rozina
Nie o to proszono. Wykonuje więcej pracy niż to konieczne.
Alex Worden
8
Może to zrobić więcej niż to, o co proszono, ale robi dokładnie to, co chciałem (uzyskać mapę różnych elementów na liście do ich liczby). Co więcej, to pytanie było najlepszym wynikiem w Google, kiedy szukałem.
KJP
@rozina Wszystkie obliczenia otrzymasz za jednym razem.
atoMerz
22

To pokazuje, dlaczego ważne jest, aby „ Odwołać się do obiektów za pomocą ich interfejsów ”, jak opisano w książce Effective Java .

Jeśli zakodujesz implementację i użyjesz ArrayList w powiedzmy 50 miejscach w swoim kodzie, kiedy znajdziesz dobrą implementację "List", która liczy elementy, będziesz musiał zmienić wszystkie te 50 miejsc i prawdopodobnie będziesz musiał złamać swój kod (jeśli jest używany tylko przez Ciebie, nie ma problemu, ale jeśli jest używany przez kogoś innego, złamiesz też jego kod)

Programując w interfejsie, możesz pozostawić te 50 miejsc bez zmian i zamienić implementację z ArrayList na „CountItemsList” (na przykład) lub inną klasę.

Poniżej znajduje się bardzo podstawowy przykład, jak można to napisać. To tylko próbka, lista gotowa do produkcji byłaby znacznie bardziej skomplikowana.

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

Zastosowane tu zasady OO: dziedziczenie, polimorfizm, abstrakcja, hermetyzacja.

OscarRyz
źródło
12
Cóż, zawsze powinno się próbować raczej kompozycji niż dziedziczenia. Twoja implementacja utknęła teraz w ArrayList, gdy może się zdarzyć, że potrzebujesz LinkedList lub innego. Twój przykład powinien pobrać inną listę LIst w swoim konstruktorze / fabryce i zwrócić opakowanie.
mP.
Całkowicie się z Tobą zgadzam. Powodem, dla którego użyłem dziedziczenia w przykładzie, jest to, że dużo łatwiej jest pokazać działający przykład przy użyciu dziedziczenia niż kompozycji (konieczność zaimplementowania interfejsu List). Dziedziczenie tworzy najwyższe sprzężenie.
OscarRyz
2
Ale nadając mu nazwę CountItemsList, sugerujesz, że robi dwie rzeczy, liczy elementy i jest listą. Myślę, że tylko jedna odpowiedzialność za tę klasę, liczenie wystąpień, byłaby tak prosta i nie musiałbyś implementować interfejsu List.
flob
11

Przepraszamy, nie ma prostej metody, która może to zrobić. Wszystko, co musisz zrobić, to stworzyć mapę i policzyć za jej pomocą częstotliwość.

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
źródło
To naprawdę nie jest skalowalne rozwiązanie - wyobraź sobie, że zbiór danych MM miał setki i tysiące wpisów, a MM chciał znać częstotliwości dla każdego wpisu. Może to być potencjalnie bardzo kosztowne zadanie - zwłaszcza, gdy istnieją znacznie lepsze sposoby na zrobienie tego.
mP.
Tak, to może nie być dobre rozwiązanie, nie oznacza, że ​​jest złe.
Adeel Ansari
1
@dehmann, nie sądzę, że on dosłownie chce liczby wystąpień nietoperzy w 4-elementowej kolekcji, myślę, że to były tylko przykładowe dane, więc lepiej zrozumielibyśmy :-).
paxdiablo
2
@Vinegar 2/2. Programowanie polega na tym, aby robić rzeczy poprawnie teraz, więc nie będziemy powodować bólów głowy ani złych doświadczeń dla kogoś innego, czy to użytkownika, czy innego programisty w przyszłości. PS: Im więcej kodu napiszesz, tym większa szansa, że ​​coś pójdzie nie tak.
mP.
2
@mP: Proszę wyjaśnić, dlaczego nie jest to skalowalne rozwiązanie. Ray Hidayat tworzy licznik częstotliwości dla każdego tokena, aby można było następnie wyszukać każdy z nich. Jakie jest lepsze rozwiązanie?
stackoverflowuser2010
10

W Javie nie ma natywnej metody, która mogłaby to zrobić za Ciebie. Możesz jednak użyć IterableUtils # countMatches () z Apache Commons-Collections, aby zrobić to za siebie.

Kevin
źródło
Zapoznaj się z moją odpowiedzią poniżej - poprawną odpowiedzią jest użycie struktury, która wspiera ideę liczenia od początku, zamiast liczenia wpisów od początku do końca za każdym razem, gdy zadawane jest zapytanie.
mP.
@mP Więc po prostu przegłosujesz każdego, kto ma inne zdanie niż ty? A co, jeśli z jakiegoś powodu nie może użyć torby lub utknie przy używaniu jednej z rodzimych kolekcji?
Kevin
-1 za bycie przegranym :-) Myślę, że mP przegłosował cię, ponieważ twoje rozwiązanie kosztuje czas za każdym razem, gdy chcesz wyniku. Torba kosztuje trochę czasu tylko przy włożeniu. Podobnie jak bazy danych, tego rodzaju struktury są „bardziej do odczytu niż do zapisu”, dlatego warto skorzystać z opcji tanich.
paxdiablo
Wygląda na to, że twoja odpowiedź wymaga również materiałów obcych, więc twój komentarz wydaje się trochę dziwny.
paxdiablo
Dzięki wam obojgu. Uważam, że jedno z dwóch podejść lub oba mogą działać. Jutro spróbuję.
MM.
9

Właściwie klasa Collection ma statyczną metodę o nazwie: frequency (Collection c, Object o), która zwraca liczbę wystąpień elementu, którego szukasz, nawiasem mówiąc, to zadziała idealnie dla Ciebie:

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
źródło
27
Lars Andren opublikował tę samą odpowiedź 5 lat przed twoją.
Fabian Barney
9

Alternatywne rozwiązanie Java 8 wykorzystujące strumienie :

long count = animals.stream().filter(animal -> "bat".equals(animal)).count();
Cristina
źródło
8

Zastanawiam się, dlaczego nie możesz używać tego Google Collection API z JDK 1.6. Czy tak jest napisane? Myślę, że możesz, nie powinno być żadnych problemów ze zgodnością, ponieważ jest zbudowany dla niższej wersji. Sprawa wyglądałaby inaczej, gdyby zostały skompilowane dla wersji 1.6 i używasz wersji 1.5.

Czy gdzieś się mylę?

Adeel Ansari
źródło
Wyraźnie wspomnieli, że są w trakcie uaktualniania swojego api do jdk 1.6.
MM.
1
To nie sprawia, że ​​stare są niekompatybilne. Czy to?
Adeel Ansari
Nie powinno. Ale sposób, w jaki rzucali stopkami, sprawia, że ​​czuję się niekomfortowo w ich wersji 0.9
MM.
Używamy go z 1.6. Gdzie jest napisane, że jest kompatybilny tylko z 1.5?
Patrick,
2
Przez „aktualizację do wersji 1.6” prawdopodobnie oznaczają „aktualizację w celu wykorzystania nowych funkcji w wersji 1.6”, a nie „naprawianie zgodności z wersją 1.6”.
Adam Jaskiewicz
6

Może być nieco bardziej wydajne podejście

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
źródło
6

Aby uzyskać wystąpienia obiektu bezpośrednio z listy:

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

Aby uzyskać wystąpienie kolekcji Object inside list, nadpisz metodę equals w klasie Object jako:

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

Zadzwoń do Collections.frequency jako:

int noOfOccurs = Collections.frequency(animals, new Animals(1));
atr
źródło
6

Prosty sposób na znalezienie wystąpienia wartości ciągu w tablicy przy użyciu funkcji 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);
    }

Wynik: {Kot = 2, Koza = 1, Krowa = 1, krowa = 1, Pies = 1}

Możesz zauważyć, że "Krowa" i krowa nie są traktowane jako ten sam ciąg, jeśli potrzebujesz tego z tą samą liczbą, użyj .toLowerCase (). Znajdź poniższy fragment dla tego samego.

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

Wynik: {kot = 2, krowa = 2, koza = 1, pies = 1}

Eswaran Venkatesan
źródło
nit: ponieważ lista jest listą łańcuchów, toString()jest niepotrzebna. Możesz po prostu zrobić:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad
5

To, czego chcesz, to torba - która jest jak zestaw, ale liczy również liczbę wystąpień. Niestety framework java Collections - świetny, ponieważ nie ma implantu Bag. W tym celu należy użyć tekstu łącza Apache Common Collection

poseł.
źródło
1
Najlepsze skalowalne rozwiązanie, a jeśli nie możesz korzystać z materiałów innych firm, po prostu napisz własne. Tworzenie toreb to nie nauka o rakietach. +1.
paxdiablo
Negocjowane za udzielenie niejasnej odpowiedzi, podczas gdy inne dostarczyły implementacje struktur danych zliczających częstotliwość. Struktura danych „torba”, z którą się łączyłeś, również nie jest odpowiednim rozwiązaniem pytania PO; ta struktura „worka” ma na celu pomieścić określoną liczbę kopii tokena, a nie zliczać liczby wystąpień tokenów.
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");

Metoda 1:

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

for (String s : set) {

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

Metoda 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
źródło
Witamy w Stack Overflow! Rozważ wyjaśnienie swojego kodu, aby ułatwić innym zrozumienie Twojego rozwiązania.
Antymon
2

Jeśli używasz kolekcji Eclipse , możesz użyć pliku Bag. A MutableBagmożna zwrócić z dowolnej implementacji RichIterableprzez wywołanie 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"));

HashBagRealizacja w Eclipse Kolekcje jest poparte MutableObjectIntMap.

Uwaga: jestem promotorem Eclipse Collections.

Donald Raab
źródło
1

Umieść elementy arraylisty w tablicy mieszania, aby policzyć częstotliwość.

Shamik
źródło
To jest dokładnie to samo, co mówi tweakt z próbką kodu.
mP.
1

Java 8 - inna metoda

String searched = "bat";
long n = IntStream.range(0, animals.size())
            .filter(i -> searched.equals(animals.get(i)))
            .count();
ROMANIA_engineer
źródło
0

Więc zrób to w staromodny sposób i stwórz własny:

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
źródło
W razie potrzeby odpowiednio „zsynchronizowany”, aby uniknąć warunków wyścigu. Ale nadal wolałbym zobaczyć to w swojej własnej klasie.
paxdiablo
Masz literówkę. Zamiast tego potrzebujesz HashMap, ponieważ bierzesz ją na mapie. Ale błąd polegający na umieszczeniu 0 zamiast 1 jest nieco poważniejszy.
Adeel Ansari
0

Jeśli jesteś użytkownikiem mojego ForEach DSL , możesz to zrobić za pomocą Countzapytania.

Count<String> query = Count.from(list);
for (Count<Foo> each: query) each.yield = "bat".equals(each.element);
int number = query.result();
akuhn
źródło
0

Nie chciałem utrudniać tej sprawy i sprawiłem, że za pomocą dwóch iteratorów mam HashMap z LastName -> FirstName. A moja metoda powinna usunąć elementy z 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
źródło
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);

Wynik:

=mp= {Ram=2, Boss=1, Shiv=1}
Ramling Muley
źródło
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
źródło
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);
    }
}

Wyjście: 4

MD EMRUL EMRAN
źródło
Dobrą praktyką w przypadku przepełnienia stosu jest dodanie wyjaśnienia, dlaczego Twoje rozwiązanie powinno działać lub jest lepsze od istniejących rozwiązań. Aby uzyskać więcej informacji, przeczytaj artykuł Jak odpowiedzieć .
Samuel Liew