2009-02-03 4 views
123

У меня есть ArrayList, класс Сборник Java следующим образом:Как подсчитать количество вхождений элемента в списке

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

Как вы можете видеть, animalsArrayList состоит из 3 bat элементов и один элемент owl. Мне было интересно, есть ли какой-либо API в структуре Collection, который возвращает число вхождений bat или если есть другой способ определить количество вхождений.

Я обнаружил, что у Google Multiset есть API, который возвращает общее количество вхождений элемента. Но это совместимо только с JDK 1.5. Наш продукт в настоящее время находится в JDK 1.6, поэтому я не могу его использовать.

+0

Это одна из причин, почему вы должны программировать на интерфейс, а не реализации. Если вам удастся найти нужную коллекцию, вам нужно будет изменить тип, чтобы использовать эту коллекцию. Я отвечу на этот вопрос. – OscarRyz

ответ

7

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

+0

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

+0

@ mP Итак, вы просто опускаете всех, у кого другое мнение, чем у вас? Что, если он по какой-то причине не может использовать Сумку или застрял в использовании одной из родных коллекций? – Kevin

+0

Не заслуживает нисходящего, ИМХО. –

10

Извините, нет простого вызова метода, который может это сделать. Все, что вам нужно сделать, это создать карту и подсчитать частоту с ней.

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

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

+0

Да, это может быть не лучшее решение, это не значит, что это неправильно. –

+0

Он просто хочет количество «бит» вхождения. Просто повторяйте один раз над оригинальным ArrayList и увеличивайте счетчик каждый раз, когда вы видите «bat». – Frank

5

Что вы хотите - это сумка, которая похожа на комплект, но также учитывает количество событий. К сожалению, структура коллекций java - отлично, поскольку у них нет сумки. Для этого нужно использовать Apache Common Collection link text

+1

Лучшее масштабируемое решение и, если вы не можете использовать сторонний материал, просто напишите свой собственный. Сумки не представляют собой ракетную науку. +1. – paxdiablo

+0

Downvoted для предоставления некоторого неопределенного ответа, в то время как другие предоставили реализации для структуры данных, учитывающих частоту. Связанная с вами структура данных «мешок» также не является подходящим решением для вопроса OP; что структура «мешка» предназначена для хранения определенного количества копий токена, а не для подсчета количества входов токенов. – stackoverflowuser2010

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

При необходимости «синхронизируется», если необходимо, чтобы избежать условий гонки. Но я бы предпочел увидеть это в своем классе. – paxdiablo

+0

У вас есть опечатка. Вместо этого нужен HashMap, поскольку вы берете его на карте. Но ошибка поставить 0 вместо 1 немного более серьезная. –

8

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

Я что-то не так?

+0

Они ясно упомянули, что они находятся в процессе обновления своих api до jdk 1.6. –

+1

Это не делает старые несовместимые. Имеет ли это? –

+0

Не следует. Но так, как они бросали отказ от ответственности, мне неудобно использовать его в своей версии 0.9. –

1

Поместите элементы arraylist в hashMap, чтобы подсчитать частоту.

+0

Это то же самое, что и tweakt с образцом кода. –

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

Это показывает, почему это важно «Refer to objects by their interfaces», как описано в книге Effective Java.

Если вы указали на реализацию и используете ArrayList, скажем, 50 мест в вашем коде, когда вы найдете хорошую реализацию «Список», которая подсчитывает элементы, вам придется изменить все эти 50 мест, и, вероятно, вы 'll придется разорвать ваш код (если он используется только вами, не имеет большого значения, но если он используется кем-то другим, вы также сломаете его код)

Путем программирования интерфейса вы могут оставить эти 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")); 
    } 
} 

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

+11

Ну, всегда нужно попробовать композицию, а не наследование. Ваша реализация теперь привязана к ArrayList, когда могут появиться ссылки LinkedList или другие. Ваш пример должен был взять еще один LIst в его конструкторе/фабрике и вернуть оболочку. –

+0

Я полностью согласен с вами. Причина, по которой я использовал наследование в примере, состоит в том, что гораздо проще показать пример выполнения с использованием наследования, чем состав (для реализации интерфейса List). Наследование создает наивысшую связь. – OscarRyz

+2

Но, назвав его CountItemsList, вы подразумеваете, что он выполняет две вещи: он подсчитывает элементы и представляет собой список. Я думаю, что одна единственная ответственность за этот класс, считая вхождения, будет такой же простой, и вам не нужно будет реализовывать интерфейс List. – flob

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

Я уверен, что статическая частота-метод в коллекции пригодится здесь:

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

Вот как я это сделать в любом случае. Я уверен, что это jdk 1.6 прямо вверх.

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} 
47

В Java 8:

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

Использование функции .identity() (со статическим импортом) вместо e -> e делает его немного приятнее для чтения. – Kuchi

+7

Почему это лучше, чем 'Collections.frequency()'?Это кажется менее читаемым. – rozina

+0

Это не то, о чем просили. Он делает больше работы, чем необходимо. –

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 реализация в ЕС опирается на MutableObjectIntMap.

Примечание: Я коммиттер для коллекций Eclipse.

10

На самом деле, Коллекция класс имеет статический метод: частоты (Сборник с, Object о), который возвращает число вхождений элемента, который вы ищете, кстати, это будет отлично работать для вас:

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

Ларс Андрен отправил тот же ответ за 5 лет до твоего. –

2

Java-8 - другой метод

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

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

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

Чтобы получить вхождения объекта из списка непосредственно:

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

Я не хотел, чтобы этот случай был сложнее и сделал его с двумя итераторами У меня есть HashMap с LastName -> FirstName. И мой метод должен удалять элементы с 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(); 

    } 

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

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

0

Простой способ найти вхождение значения строки в массиве с использованием 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); 
    } 

Выход: {Cat = 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}

Смежные вопросы