วิธีการนับจำนวนที่เกิดขึ้นขององค์ประกอบในรายการ


173

ฉันมีArrayListคลาส Collection ของ 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 จิม

105

ใน Java 8:

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

6
การใช้ Function.identity () (พร้อมการนำเข้าคงที่) แทน e -> e ทำให้การอ่านดีขึ้นเล็กน้อย
Kuchi

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

หลักการของ OO ถูกนำไปใช้ที่นี่: การสืบทอด, ความหลากหลาย, สิ่งที่เป็นนามธรรม, การห่อหุ้ม


12
อย่างใดอย่างหนึ่งควรลององค์ประกอบมากกว่ามรดก การใช้งานของคุณติดอยู่กับ ArrayList เมื่อมีเวลาที่คุณต้องการ LinkedList หรืออื่น ๆ ตัวอย่างของคุณควรมี LIst อื่นในคอนสตรัคเตอร์ / โรงงานและส่งคืนเสื้อคลุม
mP

ฉันเห็นด้วยกับคุณ. เหตุผลที่ฉันใช้การสืบทอดในตัวอย่างเป็นเพราะง่ายต่อการแสดงตัวอย่างการรันโดยใช้การสืบทอดมากกว่าการเรียบเรียง (ต้องใช้อินเตอร์เฟสรายการ) การสืบทอดสร้างการเชื่อมต่อสูงสุด
OscarRyz

2
แต่ด้วยการตั้งชื่อมันว่า CountItemsList คุณหมายถึงว่ามันทำสองสิ่งมันนับรายการและมันเป็นรายการ ฉันคิดว่าความรับผิดชอบเพียงอย่างเดียวเดียวสำหรับชั้นเรียนนั้นการนับเหตุการณ์ที่เกิดขึ้นจะเป็นเรื่องง่ายและคุณไม่จำเป็นต้องใช้ส่วนต่อประสานรายการ
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 มีหลายร้อยรายการและ MM ต้องการทราบความถี่สำหรับแต่ละรายการ นี่อาจเป็นงานที่มีค่าใช้จ่ายสูงโดยเฉพาะอย่างยิ่งเมื่อมีวิธีที่ดีกว่าในการทำเช่นนั้น
mP

ใช่มันอาจไม่ใช่ทางออกที่ดีไม่ได้หมายความว่าผิด
Adeel Ansari

1
@dehmann ฉันไม่คิดว่าเขาต้องการจำนวนครั้งที่ปรากฏของค้างคาวในคอลเลกชัน 4 องค์ประกอบฉันคิดว่านั่นเป็นเพียงข้อมูลตัวอย่างเพื่อให้เราเข้าใจดียิ่งขึ้น :-)
paxdiablo

2
@Vinegar 2/2 การเขียนโปรแกรมเกี่ยวกับการทำสิ่งต่าง ๆ อย่างถูกต้องในขณะนี้ดังนั้นเราจึงไม่ก่อให้เกิดอาการปวดหัวหรือประสบการณ์ที่ไม่ดีสำหรับคนอื่นไม่ว่าจะเป็นผู้ใช้หรือผู้ทำรหัสอีกคนในอนาคต PS: ยิ่งคุณเขียนรหัสมากเท่าไหร่ก็ยิ่งมีโอกาสมากขึ้นที่จะมีบางอย่างผิดพลาด
mP

2
@mP: โปรดอธิบายว่าทำไมนี่จึงไม่ใช่โซลูชันที่ปรับขนาดได้ Ray Hidayat กำลังสร้างจำนวนความถี่สำหรับแต่ละโทเค็นเพื่อให้สามารถค้นหาแต่ละโทเค็นได้ ทางออกที่ดีกว่าคืออะไร
stackoverflowuser2010

10

ไม่มีวิธีเนทีฟใน Java ที่จะทำเพื่อคุณ อย่างไรก็ตามคุณสามารถใช้IterableUtils # countMatches ()จาก Apache Commons-Collections เพื่อทำเพื่อคุณ


อ้างถึงคำตอบของฉันด้านล่าง - คำตอบที่ถูกต้องคือการใช้โครงสร้างที่รองรับแนวคิดการนับจากจุดเริ่มต้นแทนที่จะนับรายการตั้งแต่ต้นจนจบแต่ละครั้งที่มีการสืบค้น
mP

@mP ดังนั้นคุณเพียงลงคะแนนให้ทุกคนที่มีความคิดเห็นที่แตกต่างจากคุณ? ถ้าเขาไม่สามารถใช้กระเป๋าด้วยเหตุผลบางอย่างหรือติดอยู่กับการใช้หนึ่งในคอลเลกชันพื้นเมือง?
เควิน

-1 สำหรับการเป็นผู้แพ้ :-) ฉันคิดว่า mP downvoted คุณเพราะโซลูชันของคุณมีค่าใช้จ่ายทุกครั้งที่คุณต้องการผลลัพธ์ ถุงเสียเวลาเล็กน้อยเมื่อใส่เท่านั้น เช่นเดียวกับฐานข้อมูลโครงสร้างแบบนี้มีแนวโน้มที่จะ "อ่านมากกว่าเขียน" ดังนั้นจึงเหมาะสมที่จะใช้ตัวเลือกต้นทุนต่ำ
paxdiablo

และดูเหมือนว่าคำตอบของคุณยังต้องการสิ่งที่ไม่ใช่เจ้าของภาษาด้วยดังนั้นความคิดเห็นของคุณจึงดูแปลก ๆ
paxdiablo

ขอบคุณพวกคุณทั้งคู่ ฉันเชื่อว่าหนึ่งในสองวิธีการหรือทั้งสองวิธีอาจทำงานได้ พรุ่งนี้ฉันจะลองดูนะ
MM

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
Lars Andren โพสต์คำตอบเดียวกัน 5 ปีก่อนหน้าคุณ
เฟเบียนบาร์นีย์


8

ฉันสงสัยว่าทำไมคุณไม่สามารถใช้ API การรวบรวมของ Google นั้นกับ JDK 1.6 ได้ มันพูดอย่างนั้นเหรอ? ฉันคิดว่าคุณทำได้ไม่ควรมีปัญหาความเข้ากันได้เนื่องจากมันสร้างขึ้นสำหรับรุ่นที่ต่ำกว่า กรณีจะแตกต่างกันไปหากสร้างขึ้นสำหรับ 1.6 และคุณกำลังใช้งาน 1.5

ฉันผิดที่ไหน


พวกเขาได้กล่าวอย่างชัดเจนว่าพวกเขาอยู่ในขั้นตอนการอัปเกรด API ของพวกเขาเป็น jdk 1.6
MM

1
แต่นั่นไม่ได้ทำให้เก่าเข้ากันไม่ได้ ทำมัน?
Adeel Ansari

มันไม่ควร แต่วิธีที่พวกเขาทิ้งข้อความปฏิเสธความรับผิดชอบทำให้ฉันรู้สึกไม่สะดวกใจที่จะใช้มันในเวอร์ชั่น 0.9
MM

เราใช้มันกับ 1.6 มันบอกว่ามันเข้ากันได้กับ 1.5 เท่านั้น?
Patrick

2
โดย "การอัพเกรดเป็น 1.6" พวกเขาอาจหมายถึง "การอัพเกรดเพื่อใช้ประโยชน์จากสิ่งใหม่ใน 1.6" ไม่ใช่ "การแก้ไขความเข้ากันได้กับ 1.6"
Adam Jaskiewicz

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

เอาต์พุต: {Cat = 2, Goat = 1, Cow = 1, cow = 1, Dog = 1}

คุณสามารถสังเกตเห็น "วัว" และวัวไม่ถือเป็นสตริงเดียวกันในกรณีที่คุณต้องการภายใต้การนับเดียวกันใช้. toLowerCase () โปรดค้นหาตัวอย่างด้านล่างสำหรับสิ่งเดียวกัน

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

เอาต์พุต: {cat = 2, cow = 2, goat = 1, dog = 1}


nit: เนื่องจากรายการเป็นรายการของสตริงtoString()จึงไม่จำเป็น คุณสามารถทำได้:duplicateList.stream().collect(Collectors.groupingBy(e -> e,Collectors.counting()));
ตาด

5

สิ่งที่คุณต้องการคือกระเป๋า - ซึ่งเป็นเหมือนชุด แต่ยังนับจำนวนครั้ง น่าเสียดายที่เฟรมเวิร์กของคอลเลกชัน java - ยอดเยี่ยมเพราะมันไม่มีแบทช์ สำหรับสิ่งนั้นจะต้องใช้ข้อความลิงก์ Apache Common Collection


1
ทางออกที่ดีที่สุดที่ปรับขนาดได้และหากคุณไม่สามารถใช้เนื้อหาของบุคคลที่สามได้ให้เขียนของคุณเอง กระเป๋าไม่ใช่วิทยาศาสตร์จรวดที่จะสร้าง +1
paxdiablo

ลดลงเพื่อให้คำตอบที่คลุมเครือในขณะที่คนอื่นจัดให้มีการใช้งานสำหรับโครงสร้างข้อมูลนับความถี่ โครงสร้างข้อมูล 'ถุง' ที่คุณเชื่อมโยงไปยังไม่ใช่ทางออกที่เหมาะสมสำหรับคำถามของ OP โครงสร้าง 'ถุง' นั้นมีวัตถุประสงค์เพื่อเก็บสำเนาของโทเค็นจำนวนหนึ่งโดยเฉพาะเพื่อไม่นับจำนวนการเกิดขึ้นของโทเค็น
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);

ยินดีต้อนรับสู่ Stack Overflow! ลองอธิบายรหัสของคุณเพื่อให้ผู้อื่นเข้าใจวิธีแก้ไขปัญหาของคุณได้ง่ายขึ้น
พลวง

2

ถ้าคุณใช้คราสคอลเลกชัน , Bagคุณสามารถใช้ MutableBagสามารถกลับมาจากการดำเนินการใด ๆโดยการเรียกRichIterabletoBag()

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

ใส่องค์ประกอบของ arraylist ใน hashMap เพื่อนับความถี่


นี่เป็นสิ่งเดียวกับที่ tweakt พูดพร้อมกับตัวอย่างโค้ด
mP


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 แทนในขณะที่คุณกำลังนำไปไว้ในแผนที่ แต่ความผิดพลาดในการใส่ 0 แทนที่จะเป็น 1 นั้นรุนแรงขึ้นเล็กน้อย
Adeel Ansari

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 พร้อมนามสกุล -> 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();

    }

}

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 เพื่อเพิ่มคำอธิบายว่าทำไมโซลูชันของคุณจึงควรทำงานหรือดีกว่าโซลูชันที่มีอยู่ สำหรับข้อมูลเพิ่มเติมอ่านวิธีการตอบ
ซามูเอล Liew
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.