วิธีที่เร็วที่สุดในการเปรียบเทียบสองชุดใน Java คืออะไร


103

ฉันกำลังพยายามเพิ่มประสิทธิภาพโค้ดส่วนหนึ่งซึ่งเปรียบเทียบองค์ประกอบของรายการ

เช่น.

public void compare(Set<Record> firstSet, Set<Record> secondSet){
    for(Record firstRecord : firstSet){
        for(Record secondRecord : secondSet){
            // comparing logic
        }
    }
}

โปรดคำนึงว่าจำนวนบันทึกในชุดจะสูง

ขอบคุณ

เชคาร์


7
เป็นไปไม่ได้ที่จะปรับลูปให้เหมาะสมโดยไม่ทราบ (และปรับเปลี่ยน) ตรรกะการเปรียบเทียบ คุณสามารถแสดงรหัสของคุณเพิ่มเติมได้ไหม
josefx

คำตอบ:


161
firstSet.equals(secondSet)

มันขึ้นอยู่กับสิ่งที่คุณต้องการทำในตรรกะการเปรียบเทียบ ... นั่นคือจะเกิดอะไรขึ้นถ้าคุณพบองค์ประกอบในชุดหนึ่งไม่อยู่ในอีกชุดหนึ่ง? วิธีการของคุณมีvoidประเภทการส่งคืนดังนั้นฉันถือว่าคุณจะทำงานที่จำเป็นในวิธีนี้

การควบคุมที่ละเอียดยิ่งขึ้นหากคุณต้องการ:

if (!firstSet.containsAll(secondSet)) {
  // do something if needs be
}
if (!secondSet.containsAll(firstSet)) {
  // do something if needs be
}

หากคุณต้องการได้รับองค์ประกอบที่อยู่ในชุดเดียวไม่ใช่ชุดอื่น
แก้ไข: set.removeAll(otherSet)ส่งคืนบูลีนไม่ใช่ชุด ในการใช้ removeAll () คุณจะต้องคัดลอกชุดจากนั้นจึงใช้งานได้

Set one = new HashSet<>(firstSet);
Set two = new HashSet<>(secondSet);
one.removeAll(secondSet);
two.removeAll(firstSet);

หากเนื้อหาoneและtwoว่างเปล่าคุณจะรู้ว่าทั้งสองชุดมีค่าเท่ากัน ถ้าไม่แสดงว่าคุณมีองค์ประกอบที่ทำให้ชุดไม่เท่ากัน

คุณกล่าวว่าจำนวนบันทึกอาจสูง หากการนำไปใช้งานเป็นพื้นฐานการHashSetดึงข้อมูลแต่ละระเบียนจะเสร็จสิ้นในO(1)เวลาดังนั้นคุณจึงไม่สามารถทำได้ดีไปกว่านั้น TreeSetคือO(log n).


3
การใช้ equals () และ hashcode () สำหรับคลาส Record มีความสำคัญเท่าเทียมกันเมื่อเรียกใช้ equals () บน Set
Vineet Reynolds

1
ฉันไม่แน่ใจว่าตัวอย่าง removeAll () ถูกต้อง removeAll () ส่งคืนบูลีนไม่ใช่ชุดอื่น องค์ประกอบใน secondSet จะถูกลบออกจาก firstSet และ true จะถูกส่งกลับหากมีการเปลี่ยนแปลง
Richard Corfield

4
ตัวอย่าง removeAll ยังไม่ถูกต้องเนื่องจากคุณยังไม่ได้ทำสำเนา (Set one = firstSet; Set two = secondSet) ฉันจะใช้ตัวสร้างการคัดลอก
Michael Rusch

1
ที่จริงแล้วการใช้งานเริ่มต้นequalsนั้นเร็วกว่าการเรียกสองครั้งcontainsAllในกรณีที่เลวร้ายที่สุด ดูคำตอบของฉัน
Stephen C

6
คุณต้องทำ Set one = new HashSet (firstSet) มิฉะนั้นรายการจาก firstSet และ secondSet จะถูกลบออก
Bonton255

61

หากคุณต้องการทราบว่าชุดเท่ากันหรือไม่equalsวิธีการเปิดAbstractSetจะถูกนำไปใช้โดยประมาณดังนี้:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return containsAll(c);
    }

สังเกตว่าจะเพิ่มประสิทธิภาพกรณีทั่วไปอย่างไรโดยที่:

  • วัตถุทั้งสองเหมือนกัน
  • วัตถุอื่นไม่ได้เป็นชุดเลยและ
  • ขนาดทั้งสองชุดแตกต่างกัน

หลังจากนั้นcontainsAll(...)จะส่งคืนfalseทันทีที่พบองค์ประกอบในชุดอื่นที่ไม่อยู่ในชุดนี้ด้วย แต่ถ้าองค์ประกอบทั้งหมดมีอยู่ในทั้งสองชุดก็จะต้องทดสอบองค์ประกอบทั้งหมด

ประสิทธิภาพของกรณีที่เลวร้ายที่สุดจึงเกิดขึ้นเมื่อทั้งสองชุดมีค่าเท่ากัน แต่ไม่ใช่วัตถุเดียวกัน ค่าใช้จ่ายที่เป็นปกติO(N)หรือขึ้นอยู่กับการดำเนินงานของO(NlogN)this.containsAll(c)

และคุณจะได้รับประสิทธิภาพในกรณีที่ใกล้เคียงกับที่แย่ที่สุดหากชุดมีขนาดใหญ่และมีองค์ประกอบที่แตกต่างกันเพียงเล็กน้อยเท่านั้น


อัปเดต

หากคุณยินดีที่จะลงทุนเวลาในการใช้งานชุดที่กำหนดเองมีวิธีการที่สามารถปรับปรุงกรณี "เกือบเหมือนกัน" ได้

ความคิดคือการที่คุณต้องไปก่อนคำนวณและแคชแฮสำหรับการตั้งค่าทั้งหมดเพื่อที่คุณจะได้รับค่าแฮชโค้ดของตลาดหลักทรัพย์ฯ O(1)ในปัจจุบัน จากนั้นคุณสามารถเปรียบเทียบแฮชโค้ดของทั้งสองชุดเป็นการเร่งความเร็ว

คุณจะใช้แฮชโค้ดแบบนั้นได้อย่างไร? ถ้าแฮชโค้ดที่ตั้งไว้คือ:

  • ศูนย์สำหรับเซตว่างและ
  • XOR ของแฮชโค้ดองค์ประกอบทั้งหมดสำหรับชุดที่ไม่ว่างเปล่า

จากนั้นคุณสามารถอัปเดตแฮชโค้ดที่แคชของชุดได้ในราคาถูกทุกครั้งที่คุณเพิ่มหรือลบองค์ประกอบ ในทั้งสองกรณีคุณเพียงแค่ XOR แฮชโค้ดขององค์ประกอบด้วยแฮชโค้ดชุดปัจจุบัน

แน่นอนว่าสิ่งนี้ถือว่าแฮชโค้ดขององค์ประกอบมีความเสถียรในขณะที่องค์ประกอบเป็นสมาชิกของชุด นอกจากนี้ยังถือว่าฟังก์ชัน hashcode ของคลาสองค์ประกอบให้การแพร่กระจายที่ดี นั่นเป็นเพราะเมื่อแฮชโค้ดทั้งสองชุดเหมือนกันคุณยังคงต้องถอยกลับไปสู่การO(N)เปรียบเทียบองค์ประกอบทั้งหมด


คุณสามารถนำแนวคิดนี้ไปอีกเล็กน้อย ... อย่างน้อยก็ในทางทฤษฎี

คำเตือน - เป็นการเก็งกำไรอย่างมาก "การทดลองทางความคิด" หากคุณต้องการ

สมมติว่าคลาส set element ของคุณมีวิธีการส่งคืนการตรวจสอบ crypto สำหรับองค์ประกอบ ตอนนี้ใช้การตรวจสอบของชุดโดย XORing การตรวจสอบที่ส่งคืนสำหรับองค์ประกอบ

สิ่งนี้ซื้ออะไรให้เรา?

ดีถ้าเราคิดว่าเล่ห์เหลี่ยมอะไรที่เกิดขึ้นน่าจะเป็นที่ใดสององค์ประกอบชุดที่ไม่เท่ากันมี checksums N-bit เดียวกัน 2 -N และความน่าจะเป็น 2 ชุดที่ไม่เท่ากันมี checksums N-bit เดียวกันนอกจากนี้ยังมี 2 -N ดังนั้นความคิดของฉันคือคุณสามารถใช้equalsเป็น:

    public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;
        Collection c = (Collection) o;
        if (c.size() != size())
            return false;
        return checksums.equals(c.checksums);
    }

ภายใต้สมมติฐานข้างต้นนี้จะให้คำตอบที่ผิดเพียงครั้งเดียวใน 2 -Nครั้ง หากคุณสร้าง N ให้ใหญ่พอ (เช่น 512 บิต) ความน่าจะเป็นของคำตอบที่ผิดจะกลายเป็นเรื่องเล็กน้อย (เช่นประมาณ 10 -150 )

ข้อเสียคือการคำนวณการตรวจสอบการเข้ารหัสลับสำหรับองค์ประกอบนั้นมีราคาแพงมากโดยเฉพาะเมื่อจำนวนบิตเพิ่มขึ้น ดังนั้นคุณต้องมีกลไกที่มีประสิทธิภาพในการบันทึกเช็คซัม และนั่นอาจเป็นปัญหาได้

และข้อเสียอีกประการหนึ่งคือความน่าจะเป็นของข้อผิดพลาดที่ไม่ใช่ศูนย์ ประการหนึ่งคือความน่าจะเป็นอาจไม่สามารถยอมรับได้ไม่ว่าความน่าจะเป็นจะน้อยเพียงใดก็ตาม (แต่ถ้าเป็นเช่นนั้น ... คุณจะจัดการกับกรณีที่รังสีคอสมิกพลิกบิตวิกฤตได้อย่างไรหรือถ้ามันพลิกบิตเดียวกันในสองกรณีของระบบซ้ำซ้อนพร้อมกัน)


ควรเป็นถ้า (checksumsDoNotMatch (0)) ส่งคืนเท็จ อื่นกลับ doHeavyComparisonToMakeSureTheSetsReallyMatch (o);
Esko Piirainen

ไม่จำเป็น. หากความน่าจะเป็นของการตรวจสอบสองรายการที่ตรงกันสำหรับชุดที่ไม่เท่ากันมีค่าน้อยพอที่จะข้ามการเปรียบเทียบได้ ทําคณิตศาสตร์.
Stephen C

17

มีวิธีการในฝรั่งSetsที่สามารถช่วยได้ที่นี่:

public static <E>  boolean equals(Set<? extends E> set1, Set<? extends E> set2){
return Sets.symmetricDifference(set1,set2).isEmpty();
}

5

คุณมีวิธีแก้ไขต่อไปนี้จากhttps://www.mkyong.com/java/java-how-to-compare-two-sets/

public static boolean equals(Set<?> set1, Set<?> set2){

    if(set1 == null || set2 ==null){
        return false;
    }

    if(set1.size() != set2.size()){
        return false;
    }

    return set1.containsAll(set2);
}

หรือหากคุณต้องการใช้คำสั่ง return เดี่ยว:

public static boolean equals(Set<?> set1, Set<?> set2){

  return set1 != null 
    && set2 != null 
    && set1.size() == set2.size() 
    && set1.containsAll(set2);
}

หรืออาจใช้equals()วิธีการจากAbstractSet(จัดส่งพร้อม JDK) ซึ่งเกือบจะเหมือนกับโซลูชันที่นี่ยกเว้นการตรวจสอบค่าว่างเพิ่มเติม Java-11 Set Interface
Chaithu Narayana

4

มีโซลูชัน O (N) สำหรับกรณีเฉพาะที่:

  • ทั้งสองชุดถูกจัดเรียง
  • ทั้งสองเรียงตามลำดับเดียวกัน

รหัสต่อไปนี้ถือว่าทั้งสองชุดอ้างอิงจากระเบียนที่เทียบเคียงกัน วิธีการที่คล้ายกันอาจขึ้นอยู่กับตัวเปรียบเทียบ

    public class SortedSetComparitor <Foo extends Comparable<Foo>> 
            implements Comparator<SortedSet<Foo>> {

        @Override
        public int compare( SortedSet<Foo> arg0, SortedSet<Foo> arg1 ) {
            Iterator<Foo> otherRecords = arg1.iterator();
            for (Foo thisRecord : arg0) {
                // Shorter sets sort first.
                if (!otherRecords.hasNext()) return 1;
                int comparison = thisRecord.compareTo(otherRecords.next());
                if (comparison != 0) return comparison;
            }
            // Shorter sets sort first
            if (otherRecords.hasNext()) return -1;
            else return 0;
        }
    }

3

หากคุณกำลังใช้Guavaไลบรารีคุณสามารถทำได้:

        SetView<Record> added = Sets.difference(secondSet, firstSet);
        SetView<Record> removed = Sets.difference(firstSet, secondSet);

จากนั้นจึงสรุปตามสิ่งเหล่านี้


2

ฉันจะใส่ secondSet ใน HashMap ก่อนการเปรียบเทียบ วิธีนี้จะช่วยลดเวลาในการค้นหาของรายการที่สองเป็น n (1) แบบนี้:

HashMap<Integer,Record> hm = new HashMap<Integer,Record>(secondSet.size());
int i = 0;
for(Record secondRecord : secondSet){
    hm.put(i,secondRecord);
    i++;
}
for(Record firstRecord : firstSet){
    for(int i=0; i<secondSet.size(); i++){
    //use hm for comparison
    }
}

หรือคุณสามารถใช้อาร์เรย์แทนแฮชแมปสำหรับรายการที่สอง
Sahin Habesoglu

และวิธีนี้จะถือว่าชุดไม่ได้เรียงลำดับ
Sahin Habesoglu

1
public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Set))
            return false;

        Set<String> a = this;
        Set<String> b = o;
        Set<String> thedifference_a_b = new HashSet<String>(a);


        thedifference_a_b.removeAll(b);
        if(thedifference_a_b.isEmpty() == false) return false;

        Set<String> thedifference_b_a = new HashSet<String>(b);
        thedifference_b_a.removeAll(a);

        if(thedifference_b_a.isEmpty() == false) return false;

        return true;
    }

-1

ฉันคิดว่าการอ้างอิงเมธอดด้วยวิธีการเท่ากับสามารถใช้ได้ เราถือว่าประเภทวัตถุที่ปราศจากเงาของข้อสงสัยมีวิธีการเปรียบเทียบของตัวเอง ตัวอย่างธรรมดาและเรียบง่ายอยู่ที่นี่

Set<String> set = new HashSet<>();
set.addAll(Arrays.asList("leo","bale","hanks"));

Set<String> set2 = new HashSet<>();
set2.addAll(Arrays.asList("hanks","leo","bale"));

Predicate<Set> pred = set::equals;
boolean result = pred.test(set2);
System.out.println(result);   // true

1
นี่เป็นวิธีที่ซับซ้อนในการพูดset.equals(set2)
Alex
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.