Как посчитать количество вхождений элемента в список

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 в структуре Collection, который возвращает количество batвхождений или есть другой способ определить количество вхождений.

Я обнаружил, что в Google Collection 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/…
Миньон Джим
105

В Java 8:

Map<String, Long> counts =
    list.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting()));
Виталий Федоренко
источник
6
Использование Function.identity () (со статическим импортом) вместо e -> e делает чтение более приятным.
Кучи
8
Почему это лучше чем Collections.frequency()? Это кажется менее читабельным.
Розина
Это не то, что просили. Это делает больше работы, чем необходимо.
Алекс Уорден
8
Это может сделать больше, чем было запрошено, но это именно то, что я хотел (получить карту отдельных элементов в списке по их количеству). Кроме того, этот вопрос был лучшим результатом в Google, когда я искал.
KJP
@rozina Вы получаете все счета за один проход.
atoMerz
22

Это показывает, почему важно « обращаться к объектам по их интерфейсам », как описано в книге « Эффективное Java» .

Если вы кодируете реализацию и используете ArrayList, скажем, в 50 местах в вашем коде, когда вы найдете хорошую реализацию «List», которая подсчитывает элементы, вам придется изменить все эти 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" ));
    }
}

Применяемые здесь ОО-принципы: наследование, полиморфизм, абстракция, инкапсуляция.

OscarRyz
источник
12
Ну, всегда нужно пробовать композицию, а не наследование. Ваша реализация теперь привязана к ArrayList, когда могут возникнуть ситуации, когда вам понадобится LinkedList или другой. Ваш пример должен был взять другой LIst в его конструкторе / фабрике и вернуть оболочку.
мП
Я полностью с вами согласен. Причина, по которой я использовал наследование в этом примере, заключается в том, что гораздо проще показать работающий пример, используя наследование, чем композицию (для реализации интерфейса List). Наследование создает самую высокую связь.
OscarRyz
2
Но, называя его CountItemsList, вы подразумеваете, что он делает две вещи, он считает элементы и является списком. Я думаю, что одна единственная ответственность за этот класс, считая вхождения, была бы такой же простой, и вам не нужно было бы реализовывать интерфейс List.
флоп
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 содержит сотни и тысячи записей, и MM хотела знать частоты для каждой записи. Это потенциально может быть очень дорогостоящей задачей, особенно когда есть гораздо лучшие способы сделать это.
мП
Да, это не может быть хорошим решением, не означает, что это неправильно.
Адел Ансари
1
@dehmann, я не думаю, что он в буквальном смысле хочет узнать количество появлений летучих мышей в коллекции из 4 элементов, я думаю, что это были просто примерные данные, чтобы мы лучше понимали :-).
paxdiablo
2
@ Уксус 2/2. Программирование - это то, что нужно делать правильно сейчас, поэтому мы не будем вызывать головные боли или плохой опыт для кого-то еще, будь то пользователь или другой программист в будущем. PS: Чем больше кода вы пишете, тем больше шансов, что что-то может пойти не так.
мП
2
@mP: Пожалуйста, объясните, почему это не масштабируемое решение. Рэй Хидайт строит счетчик частот для каждого токена, чтобы каждый токен можно было искать. Какое решение лучше?
stackoverflowuser2010
10

В Java нет нативного метода сделать это за вас. Однако вы можете использовать IterableUtils # countMatches () из Apache Commons-Collections, чтобы сделать это за вас.

Kevin
источник
Обратитесь к моему ответу ниже - правильный ответ - использовать структуру, которая поддерживает идею подсчета с самого начала, а не подсчет записей от начала до конца каждый раз, когда делается запрос.
мП
@mP То есть, вы просто отрицаете мнение всех, у кого другое мнение, чем у вас? Что если он не может использовать Сумку по какой-то причине или застрял с использованием одной из родных Коллекций?
Кевин
-1 за то, что я неудачник :-) Я думаю, что mP отказал вам, потому что ваше решение стоит времени каждый раз, когда вы хотите получить результат. Сумка стоит немного времени только при вставке. Подобно базам данных, структуры такого типа, как правило, «больше читаются, чем пишут», поэтому имеет смысл использовать опцию низкой стоимости.
paxdiablo
И, похоже, ваш ответ также требует не родных вещей, поэтому ваш комментарий кажется немного странным.
paxdiablo
Спасибо вам обоим, ребята. Я считаю, что один из двух подходов или оба могут работать. Я попробую завтра.
ММ.
9

На самом деле, класс Collections имеет статический метод с именем : quency (Collection c, Object 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"));
Khafaga
источник
27
Тот же ответ Ларс Андрен опубликовал за 5 лет до вашего.
Фабиан Барни
9

Альтернативное решение Java 8 с использованием Streams :

long count = animals.stream().filter(animal -> "bat".equals(animal)).count();
Cristina
источник
8

Интересно, почему вы не можете использовать этот API Google Collection с JDK 1.6. Так ли это? Я думаю, что вы можете, не должно быть никаких проблем с совместимостью, так как он создан для более низкой версии. Случай был бы другим, если бы он был построен для 1.6, а вы используете 1.5.

Я где то не прав?

Адель Ансари
источник
Они четко упомянули, что находятся в процессе обновления API до версии 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");

Чтобы получить вхождение коллекции Object в списке, переопределите метод 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()));
Tad
5

То, что вы хотите, - это сумка, которая похожа на набор, но при этом подсчитывает количество случаев. К сожалению, фреймворк java Collections - великолепен, так как у него нет пакета Bag. Для этого нужно использовать текст ссылки Apache Common Collection

депутатах.
источник
1
Лучшее масштабируемое решение и, если вы не можете использовать сторонние материалы, просто напишите свое. Сумки - это не ракетостроение. +1.
paxdiablo
Понравился за то, что дал какой-то расплывчатый ответ, в то время как другие предоставили реализации для структур данных с подсчетом частоты. Структура данных «bag», с которой вы связаны, также не является подходящим решением вопроса OP; эта структура «bag» предназначена для хранения определенного количества копий токена, а не для подсчета количества появлений токенов.
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);
SABM
источник
Добро пожаловать в стек переполнения! Попробуйте объяснить свой код, чтобы другим было проще понять ваше решение.
Сурьма
2

Если вы используете Eclipse Collections , вы можете использовать Bag. A 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 Collections.

Дональд Рааб
источник
1

Поместите элементы массива в hashMap для подсчета частоты.

Shamik
источник
Это то же самое, что говорит твикт с примером кода.
мП
1

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

String searched = "bat";
long n = IntStream.range(0, animals.size())
            .filter(i -> searched.equals(animals.get(i)))
            .count();
ROMANIA_engineer
источник
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();
akuhn
источник
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");
}
fcm45
источник
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

MD EMRUL EMRAN
источник
Хорошей практикой в ​​Stack Overflow является добавление объяснения того, почему ваше решение должно работать или лучше, чем существующие решения. Для получения дополнительной информации прочитайте, как ответить .
Самуэль Лью