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ć.


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.


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

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.


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

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.


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

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

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ę?


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

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

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}


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


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

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.


1

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


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

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

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

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

    }

}

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}

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

Wyjście: 4


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.