Як підрахувати кількість зустрічань елемента в Списку


173

У мене є ArrayListклас колекції Java таким чином:

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

Як бачите, він animals ArrayListскладається з 3-х batелементів і одного owlелемента. Мені було цікаво, чи є в рамках колекції який-небудь API, який повертає кількість batподій, чи є інший спосіб визначення кількості подій.

Я виявив, що в колекції Google Multisetє API, який повертає загальну кількість зустрічань елемента. Але це сумісно лише з JDK 1.5. Зараз наш продукт знаходиться в JDK 1.6, тому я не можу ним користуватися.


Це одна з причин, чому ви повинні запрограмувати інтерфейс, а не реалізацію. Якщо ви знайдете потрібну колекцію, вам потрібно буде змінити тип, щоб використовувати її. Я опублікую відповідь на це.
OscarRyz

Відповіді:


333

Я впевнений, що статичний частотний метод у колекціях тут стане у нагоді:

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

Ось як би я це зробив у будь-якому випадку. Я впевнений, що це jdk 1.6 прямо.


Завжди віддайте перевагу Api від JRE, які додають чергову залежність проекту. І не винаходити колесо !!
Фернандо.

Він був представлений у JDK 5 (хоча до цього ніхто не використовує версію, тому це не має значення) docs.oracle.com/javase/8/docs/technotes/guides/collections/…
Minion Jim

105

На Java 8:

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

6
Використання Function.identity () (зі статичним імпортом) замість e -> e робить його трохи приємніше читати.
Кучі

8
Чому це краще, ніж Collections.frequency()? Це здається менш читабельним.
rozina

Це не те, про що просили. Це робить більше роботи, ніж потрібно.
Alex Worden

8
Це може зробити більше, ніж просили, але це саме те, що я хотів (отримати карту різних елементів у списку до їх числа). Крім того, це питання було найкращим результатом в Google, коли я шукав.
KJP

@rozina Ви отримуєте всі рахунки за один прохід.
atoMerz

22

Це показує, чому важливо " Посилатися на об'єкти за їх інтерфейсами ", як описано в книзі Ефективна Java .

Якщо ви кодуєте реалізацію і використовуєте ArrayList, скажімо, 50 місць у вашому коді, коли ви знайдете хорошу реалізацію "Список", яка враховує елементи, вам доведеться змінити всі ці 50 місць, і, ймовірно, вам доведеться зламайте свій код (якщо він використовується лише вами - це не велика справа, але якщо його використовує хтось інший, ви також порушите його код)

За допомогою програмування на інтерфейс ви можете дозволити ті 50 місць змінитись і замінити реалізацію з ArrayList на "CountItemsList" (наприклад) або якийсь інший клас.

Нижче наведено дуже основний зразок того, як це можна було написати. Це лише зразок, Список готових до виробництва був би набагато складнішим.

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

Тут застосовуються принципи ОО: успадкування, поліморфізм, абстракція, інкапсуляція.


12
Добре завжди слід намагатись композицію, а не спадщину. Ваша реалізація тепер приклеєна до ArrayList, коли може виникнути час, коли вам потрібен LinkedList чи інше. Ваш приклад повинен був взяти інший LIst у своєму конструкторі / фабриці та повернути обгортку.
мП.

Я повністю з вами згоден. Причина, по якій я використав успадкування у вибірці, полягає в тому, що набагато простіше показувати запущений приклад за допомогою спадкування, ніж композицію (потрібно реалізувати інтерфейс списку). Спадщина створює найвищу зв’язку.
OscarRyz

2
Але, називаючи його CountItemsList, ви маєте на увазі, що він робить дві речі, він рахує елементи та це список. Я думаю, що лише одна відповідальність за цей клас, підрахунок подій, буде такою ж простою, і вам не потрібно буде реалізовувати інтерфейс List.
flob

11

На жаль, не існує простого виклику методу, який може це зробити. Все, що вам потрібно буде зробити, це створити карту і порахувати її частоту.

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

Це насправді не масштабоване рішення - уявіть, що в наборі даних MM було сотні і тисячі записів, і ММ хотів знати частоти для кожного запису. Це потенційно може бути дуже дорогим завданням - особливо, коли є набагато кращі способи це зробити.
мП.

Так, це може бути не гарним рішенням, не означає його неправильне.
Адель Ансарі

1
@dehmann, я не думаю, що він буквально хоче, щоб кількість випадків кажана в колекції з 4 елементами, я думаю, що це були лише вибіркові дані, щоб ми зрозуміли краще :-).
paxdiablo

2
@ Оцет 2/2. Програмування полягає в тому, щоб робити все належним чином, тому ми не завдаємо головних болів чи поганого досвіду комусь іншому, будь то користувачеві чи іншому кодеру в майбутньому. PS: Чим більше коду ви пишете, тим більше шансів, що щось може піти не так.
мП.

2
@mP: Будь ласка, поясніть, чому це не масштабоване рішення. Рей Хідаят будує кількість частот для кожного маркера, щоб потім кожен маркер можна було переглянути. Яке краще рішення?
stackoverflowuser2010

10

У Java немає рідного методу, який би це зробив для вас. Однак ви можете використовувати IterableUtils # countMatches () від Apache Commons-Collections, щоб зробити це за вас.


Дивіться мою відповідь нижче - правильна відповідь полягає у використанні структури, яка підтримує ідею підрахунку від початку, а не підрахунку записів від початку до кінця кожного разу, коли запит робиться.
мП.

@mP Отже, ви просто спростуєте всіх, хто має іншу думку, ніж ви? Що робити, якщо він не може використовувати сумку з якихось причин або застряг у використанні однієї з рідних колекцій?
Кевін

-1 за те, що болить боляче :-) Я думаю, що mP відмовився від вас, тому що ваше рішення коштує часу щоразу, коли ви бажаєте результату. Сумка коштувала трохи часу лише на вставлення. Як і бази даних, такі структури, як правило, «більше читаються, ніж пишуть», тому є сенс використовувати варіант з низькою вартістю.
paxdiablo

І, здається, ваша відповідь також вимагає не рідних матеріалів, тому ваш коментар здається трохи дивним.
paxdiablo

Дякую обом, хлопці. Я вважаю, що один із двох підходів або обидва вони можуть спрацювати. Я спробую завтра.
ММ.

9

Насправді клас Collections має статичний метод під назвою: частота (Колекція c, Об'єкт o), який повертає кількість входжень елемента, який ви шукаєте, до речі, це буде відмінно працювати для вас:

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
Ларс Андрен опублікував таку ж відповідь за 5 років до вашого.
Фабіан Барні

9

Альтернативне рішення Java 8 за допомогою потоків :

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

8

Цікаво, чому ви не можете використовувати цей колекційний API Google з JDK 1.6. Це так говорить? Я думаю, що ви можете, не повинно бути жодних проблем сумісності, оскільки він створений для нижчої версії. Випадок був би іншим, якби він був створений для 1.6, а ви працюєте з 1.5.

Я десь помиляюся?


Вони чітко зазначили, що вони перебувають у процесі модернізації своїх api до jdk 1.6.
ММ.

1
Це не робить старе несумісним. Робить це?
Адель Ансарі

Це не повинно. Але те, як вони кидали відмови, робить мені незручним використовувати його у своїй версії 0.9
MM.

Ми використовуємо його з 1.6. Де сказано, що він сумісний лише з 1,5?
Патрік

2
Під "оновленням до 1.6" вони, мабуть, означають "модернізацію, щоб скористатися новими новинками в 1.6", а не "фіксувати сумісність з 1.6".
Адам Яскевич

6

Трохи ефективніший підхід може бути

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

Щоб отримати вхід об’єкта зі списку безпосередньо:

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

Щоб отримати появу колекції об'єктів всередині списку, замініть метод equals у класі Object як:

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

Викличте Collections.frequency як:

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

6

Простий спосіб знайти виникнення значення рядка в масиві за допомогою функцій 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);
    }

Вихід: {Кіт = 2, Коза = 1, Корова = 1, Корова = 1, Собака = 1}

Ви можете помітити, що "Корова" та корова не вважаються однаковими рядками, якщо ви вимагаєте цього під однаковим рахунком, використовуйте .toLowerCase (). Знайдіть фрагмент нижче для того ж.

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

Вихід: {кіт = 2, корова = 2, коза = 1, собака = 1}


nit: тому що список toString()є рядком рядків, це не потрібно. Можна просто зробити:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Тад

5

Те, що ви хочете, це Сумка - це як набір, але також підраховує кількість занять. На жаль, рамки колекцій Java - чудово, оскільки вони не мають Bag Bag. Для цього потрібно використовувати текст посилання Apache Common Collection


1
Найкраще масштабоване рішення, і якщо ви не можете використовувати сторонні речі, просто напишіть своє. Мішки - це не ракетна наука для створення. +1.
paxdiablo

Запропоновано дати деяку розпливчасту відповідь, а інші запропонували реалізацію для структур даних підрахунку частоти. Структура даних "сумки", з якою ви пов’язані, також не є відповідним рішенням питання про ОП; ця структура "сумки" призначена для вміщення певної кількості копій токена, а не для підрахунку кількості зустрічей жетонів.
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");

Спосіб 1:

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

for (String s : set) {

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

Спосіб 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);

Ласкаво просимо до переповнення стека! Подумайте про пояснення свого коду, щоб іншим було легше зрозуміти ваше рішення.
Сурма

2

Якщо ви використовуєте Eclipse Collections , ви можете використовувати a Bag. А MutableBagможе бути повернуто з будь-якої реалізації RichIterable, зателефонувавши 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"));

HashBagРеалізація в Eclipse , Колекції підкріпленаMutableObjectIntMap .

Примітка. Я є членом колекції Eclipse.


1

Помістіть елементи масиву в хеш-карту для підрахунку частоти.


Це точно те саме, що tweakt говорить із зразком коду.
мП.

1

Java 8 - ще один метод

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

0

Так що робіть це старомодним способом і прокатуйте свій власний:

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

З відповідними "синхронізованими", якщо потрібно, щоб уникнути перегонів. Але я все-таки вважаю за краще бачити це у своєму класі.
paxdiablo

У вас помилка друку. Натомість потрібен HashMap, як ви берете його в Map. Але помилка ставити 0 замість 1 трохи серйозніша.
Адель Ансарі

0

Якщо ви користувач мого ForEach DSL , це можна зробити за допомогою Countзапиту.

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

0

Я не хотів ускладнювати цю справу і зробив це з двома ітераторами. У мене є HashMap з LastName -> FirstName. І мій метод повинен видалити елементи з дублікатом 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);

Вихід:

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

Вихід: 4


Додано пояснення щодо переповнення стека, щоб додати пояснення, чому ваше рішення має працювати чи краще, ніж існуючі рішення. Для отримання додаткової інформації читайте як відповісти .
Самуель Liew
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.