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


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

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


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, чтобы сделать это за вас.


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

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

27
Тот же ответ Ларс Андрен опубликовал за 5 лет до вашего.
Фабиан Барни

9

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

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

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

Добро пожаловать в стек переполнения! Попробуйте объяснить свой код, чтобы другим было проще понять ваше решение.
Сурьма

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 для подсчета частоты.


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

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


Хорошей практикой в ​​Stack Overflow является добавление объяснения того, почему ваше решение должно работать или лучше, чем существующие решения. Для получения дополнительной информации прочитайте, как ответить .
Самуэль Лью
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.