Cách đếm số lần xuất hiện của một phần tử trong Danh sách


171

Tôi có một ArrayListlớp Bộ sưu tập Java, như sau:

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

Như bạn có thể thấy, animals ArrayListbao gồm 3 batyếu tố và một owlyếu tố. Tôi đã tự hỏi nếu có bất kỳ API nào trong khung Bộ sưu tập trả về số batlần xuất hiện hoặc nếu có một cách khác để xác định số lần xuất hiện.

Tôi thấy rằng Bộ sưu tập của Google Multisetcó API trả về tổng số lần xuất hiện của một yếu tố. Nhưng điều đó chỉ tương thích với JDK 1.5. Sản phẩm của chúng tôi hiện đang ở trong JDK 1.6, vì vậy tôi không thể sử dụng nó.


Đó là một trong những lý do tại sao bạn nên lập trình lên một giao diện thay vì triển khai. Nếu bạn tình cờ tìm được bộ sưu tập phù hợp, bạn sẽ cần thay đổi loại để sử dụng bộ sưu tập đó. Tôi sẽ đăng một câu trả lời về điều này.
OscarRyz

Câu trả lời:


331

Tôi khá chắc chắn rằng phương pháp tần số tĩnh trong Bộ sưu tập sẽ có ích ở đây:

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

Đó là cách tôi sẽ làm điều đó. Tôi khá chắc chắn đây là jdk 1.6 thẳng lên.


Luôn thích Api từ JRE, thêm một phụ thuộc khác vào dự án. Và đừng phát minh lại bánh xe !!
Fernando.

Nó đã được giới thiệu trong JDK 5 (mặc dù không có ai sử dụng một phiên bản trước đó nên nó không quan trọng) docs.oracle.com/javase/8/docs/technotes/guides/collections/...
Minion Jim

104

Trong Java 8:

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

6
Sử dụng Function.identity () (với nhập tĩnh) thay vì e -> e làm cho nó dễ đọc hơn một chút.
Kuchi

8
Tại sao điều này tốt hơn Collections.frequency()? Có vẻ như ít đọc hơn.
rozina

Đây không phải là những gì được yêu cầu. Nó làm nhiều việc hơn mức cần thiết.
Alex Worden

8
Điều này có thể làm nhiều hơn những gì được yêu cầu, nhưng nó thực hiện chính xác những gì tôi muốn (lấy bản đồ của các yếu tố riêng biệt trong danh sách theo số lượng của chúng). Hơn nữa, câu hỏi này là kết quả hàng đầu trong Google khi tôi tìm kiếm.
KJP

@rozina Bạn nhận được tất cả số đếm trong một lần.
atoMerz

22

Điều này cho thấy, tại sao điều quan trọng là " Tham khảo các đối tượng bằng giao diện của chúng " như được mô tả trong sách Java hiệu quả .

Nếu bạn mã hóa việc triển khai và sử dụng ArrayList trong giả sử, 50 địa điểm trong mã của bạn, khi bạn tìm thấy triển khai "Danh sách" tốt để đếm các mục, bạn sẽ phải thay đổi tất cả 50 địa điểm đó và có lẽ bạn sẽ phải phá vỡ mã của bạn (nếu nó chỉ được sử dụng bởi bạn thì không có vấn đề gì lớn, nhưng nếu nó được sử dụng bởi người khác, bạn cũng sẽ phá vỡ mã của họ)

Bằng cách lập trình đến giao diện, bạn có thể để 50 vị trí đó không thay đổi và thay thế việc triển khai từ ArrayList thành "CountItemsList" (ví dụ) hoặc một số lớp khác.

Dưới đây là một mẫu rất cơ bản về cách này có thể được viết. Đây chỉ là một ví dụ, một sản Danh sách sẵn sàng sẽ được nhiều hơn phức tạp.

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

Nguyên tắc OO được áp dụng ở đây: kế thừa, đa hình, trừu tượng hóa, đóng gói.


12
Vâng, một người nên luôn luôn cố gắng sáng tác hơn là thừa kế. Việc triển khai của bạn hiện bị kẹt với ArrayList khi có thể đôi khi bạn muốn LinkedList hoặc khác. Ví dụ của bạn nên lấy một LIst khác trong hàm tạo / nhà máy của nó và trả về một trình bao bọc.
mP.

Tôi hoàn toàn đồng ý với bạn. Lý do tôi sử dụng tính kế thừa trong mẫu là vì việc hiển thị một ví dụ đang chạy sử dụng tính kế thừa dễ dàng hơn nhiều so với thành phần (phải thực hiện giao diện Danh sách). Kế thừa tạo ra khớp nối cao nhất.
OscarRyz

2
Nhưng bằng cách đặt tên cho nó là CountItemsList, bạn ngụ ý rằng nó thực hiện hai điều, nó đếm các mục và nó là một danh sách. Tôi nghĩ rằng chỉ cần một trách nhiệm duy nhất cho lớp đó, tính các lần xuất hiện, sẽ đơn giản và bạn sẽ không cần phải thực hiện giao diện Danh sách.
trôi nổi

11

Xin lỗi, không có cuộc gọi phương thức đơn giản nào có thể làm được. Tất cả những gì bạn cần làm là tạo một bản đồ và đếm tần số với nó.

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

Đây thực sự không phải là một giải pháp có thể mở rộng - hãy tưởng tượng tập dữ liệu của MM có hàng trăm, hàng nghìn mục và MM muốn biết các lần truy cập cho mỗi mục. Điều này có khả năng có thể là một nhiệm vụ rất tốn kém - đặc biệt là khi có nhiều cách tốt hơn để làm điều đó.
mP.

Vâng, nó có thể không phải là một giải pháp tốt, không có nghĩa là sai.
Adeel Ansari

1
@dehmann, tôi không nghĩ anh ấy thực sự muốn số lần xuất hiện của dơi trong bộ sưu tập 4 yếu tố, tôi nghĩ đó chỉ là dữ liệu mẫu để chúng tôi hiểu rõ hơn :-).
paxdiablo

2
@Vinegar 2/2. Lập trình là để thực hiện mọi thứ đúng cách ngay bây giờ, vì vậy chúng tôi không gây đau đầu hoặc trải nghiệm xấu cho người khác có thể là người dùng hoặc người viết mã khác trong tương lai. PS: Viết càng nhiều mã thì càng có nhiều khả năng xảy ra lỗi.
mP.

2
@mP: Hãy giải thích tại sao đây không phải là giải pháp mở rộng. Ray Hidayat đang xây dựng một tần số đếm cho mỗi mã thông báo để mỗi mã thông báo có thể được tra cứu. Một giải pháp tốt hơn là gì?
stackoverflowuser2010

10

Không có phương thức riêng trong Java để làm điều đó cho bạn. Tuy nhiên, bạn có thể sử dụng IterableUtils # CountMatches () từ Bộ sưu tập chung của Apache để làm điều đó cho bạn.


Tham khảo câu trả lời của tôi dưới đây - câu trả lời đúng là sử dụng cấu trúc hỗ trợ ý tưởng đếm từ lúc bắt đầu thay vì đếm các mục từ đầu đến cuối mỗi khi truy vấn được thực hiện.
mP.

@mP Vậy bạn chỉ downvote mọi người có ý kiến ​​khác với bạn? Điều gì xảy ra nếu anh ta không thể sử dụng Túi vì một số lý do hoặc bị mắc kẹt với việc sử dụng một trong các Bộ sưu tập gốc?
Kevin

-1 vì là người thua cuộc đau đớn :-) Tôi nghĩ rằng mP đã hạ thấp bạn vì giải pháp của bạn tốn thời gian mỗi khi bạn muốn có kết quả. Một túi chỉ tốn một ít thời gian khi chèn. Giống như cơ sở dữ liệu, các loại cấu trúc này có xu hướng "đọc nhiều hơn viết", vì vậy sẽ hợp lý khi sử dụng tùy chọn chi phí thấp.
paxdiablo

Và nó xuất hiện câu trả lời của bạn cũng yêu cầu những thứ không phải bản địa, vì vậy nhận xét của bạn có vẻ hơi lạ.
paxdiablo

Cảm ơn cả hai bạn. Tôi tin rằng một trong hai cách tiếp cận hoặc cả hai đều có thể hiệu quả. Tôi sẽ thử nó vào ngày mai.
MM.

9

Trên thực tế, lớp Bộ sưu tập có một phương thức tĩnh gọi là: tần số (Bộ sưu tập c, Đối tượng o) trả về số lần xuất hiện của phần tử mà bạn đang tìm kiếm, bằng cách này, điều này sẽ hoạt động hoàn hảo cho bạn:

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 đã đăng câu trả lời tương tự 5 năm trước bạn.
Fabian Barney

8

Tôi tự hỏi, tại sao bạn không thể sử dụng API Bộ sưu tập của Google với JDK 1.6. Nó có nói như vậy không? Tôi nghĩ bạn có thể, không nên có bất kỳ vấn đề tương thích nào, vì nó được xây dựng cho phiên bản thấp hơn. Trường hợp này sẽ khác nếu được xây dựng cho 1.6 và bạn đang chạy 1.5.

Tôi có sai ở đâu không?


Họ đã đề cập rõ ràng rằng họ đang trong quá trình nâng cấp api của mình lên jdk 1.6.
MM.

1
Điều đó không làm cho cũ không tương thích. Phải không?
Adeel Ansari

Nó không nên. Nhưng cách họ ném từ chối trách nhiệm, khiến tôi không thoải mái khi sử dụng nó trong phiên bản 0.9 của họ
MM.

Chúng tôi sử dụng nó với 1.6. Trường hợp nó nói nó chỉ tương thích với 1.5?
Patrick

2
Bằng cách "nâng cấp lên 1.6", họ có thể có nghĩa là "nâng cấp để tận dụng lợi thế của công cụ mới trong 1.6", chứ không phải "sửa lỗi tương thích với 1.6".
Adam Jaskiewicz

8

Giải pháp Java 8 thay thế bằng cách sử dụng Luồng :

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

6

Một cách tiếp cận hiệu quả hơn một chút có thể là

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

Để có được sự xuất hiện của đối tượng từ danh sách trực tiếp:

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

Để có được sự xuất hiện của bộ sưu tập Object bên trong danh sách, hãy ghi đè phương thức bằng trong lớp Object là:

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

Gọi Bộ sưu tập.frequency là:

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

6

Cách đơn giản để tìm sự xuất hiện của giá trị chuỗi trong một mảng bằng các tính năng 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);
    }

Đầu ra: {Cat = 2, Dê = 1, Bò = 1, bò = 1, Chó = 1}

Bạn có thể nhận thấy "Bò" và bò không được coi là cùng một chuỗi, trong trường hợp bạn yêu cầu nó dưới cùng một số lượng, hãy sử dụng .toLowerCase (). Vui lòng tìm đoạn trích dưới đây cho cùng.

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

Đầu ra: {cat = 2, cow = 2, dê = 1, dog = 1}


nit: bởi vì danh sách này là một danh sách các chuỗi, toString()không cần thiết. Bạn chỉ có thể làm:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
Tad

5

Những gì bạn muốn là một Túi - giống như một bộ nhưng cũng tính số lần xuất hiện. Thật không may, khung công tác Bộ sưu tập java - thật tuyệt vì chúng không có túi Im. Vì vậy, người ta phải sử dụng văn bản liên kết Bộ sưu tập chung của Apache


1
Giải pháp mở rộng tốt nhất và, nếu bạn không thể sử dụng công cụ của bên thứ ba, chỉ cần viết riêng của bạn. Túi không khoa học tên lửa để tạo ra. +1.
paxdiablo

Bị từ chối vì đã đưa ra một số câu trả lời mơ hồ trong khi những người khác đã cung cấp các triển khai cho các cấu trúc dữ liệu đếm tần số. Cấu trúc dữ liệu 'túi' mà bạn liên kết đến cũng không phải là một giải pháp thích hợp cho câu hỏi của OP; cấu trúc 'túi' đó nhằm giữ một số lượng bản sao cụ thể của mã thông báo, không tính số lần xuất hiện của mã thông báo.
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");

Cách 1:

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

for (String s : set) {

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

Cách 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);

Chào mừng bạn đến với Stack Overflow! Xem xét giải thích mã của bạn để giúp người khác hiểu giải pháp của bạn dễ dàng hơn.
Antimon

2

Nếu bạn sử dụng Bộ sưu tập Eclipse , bạn có thể sử dụng a Bag. A MutableBagcó thể được trả lại từ bất kỳ thực hiện RichIterablebằng cách gọi 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"));

Việc HashBagtriển khai trong các Bộ sưu tập Eclipse được hỗ trợ bởi a MutableObjectIntMap.

Lưu ý: Tôi là người đi làm cho Bộ sưu tập Eclipse.


1

Đặt các phần tử của danh sách mảng trong hashMap để đếm tần số.


Đây chính xác là điều tương tự mà tinh chỉnh nói với một mẫu mã.
mP.

1

Java 8 - một phương thức khác

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

0

Vì vậy, làm theo cách cũ và cuộn của riêng bạn:

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

Với "đồng bộ" thích hợp, nếu cần, để tránh điều kiện cuộc đua. Nhưng tôi vẫn muốn thấy điều này trong lớp học riêng của nó.
paxdiablo

Bạn có một lỗi đánh máy. Cần HashMap thay vào đó, như bạn đang dùng nó trong Map. Nhưng sai lầm khi đặt 0 thay vì 1 thì nghiêm trọng hơn một chút.
Adeel Ansari

0

Nếu bạn là người dùng DSL ForEach của tôi , nó có thể được thực hiện bằng một Counttruy vấn.

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

0

Tôi không muốn làm cho trường hợp này trở nên khó khăn hơn và làm cho nó bằng hai lần lặp Tôi có HashMap với LastName -> FirstName. Và phương pháp của tôi sẽ xóa các mục với FirstName dulicate.

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

Đầu ra:

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

Đầu ra: 4


Đó là cách thực hành tốt trên Stack Overflow để thêm một lời giải thích về lý do tại sao giải pháp của bạn nên hoạt động hoặc tốt hơn các giải pháp hiện có. Để biết thêm thông tin đọc Làm thế nào để trả lời .
Samuel Liew
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.