วิธีที่เร็วที่สุดในการเปรียบเทียบรายการทั่วไปสองรายการสำหรับความแตกต่าง


214

สิ่งที่เร็วที่สุด (และใช้ทรัพยากรอย่างน้อยที่สุด) เพื่อเปรียบเทียบสองสิ่งที่มีขนาดใหญ่ (> 50,000 รายการ) และด้วยเหตุนี้จึงมีสองรายการเหมือนรายการด้านล่าง:

  1. รายการที่ปรากฏในรายการแรก แต่ไม่ใช่ในรายการที่สอง
  2. รายการที่ปรากฏในรายการที่สอง แต่ไม่อยู่ในรายการแรก

ขณะนี้ฉันกำลังทำงานกับรายการหรือ IReadOnlyCollection และแก้ปัญหานี้ในแบบสอบถาม linq:

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

แต่มันก็ไม่ได้ผลดีเท่าที่ฉันต้องการ มีความคิดที่จะทำให้ทรัพยากรนี้เร็วขึ้นและน้อยลงอย่างที่ฉันต้องการเพื่อประมวลผลรายการจำนวนมากหรือไม่?

คำตอบ:


454

การใช้Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

ฉันสงสัยว่ามีวิธีการที่จริงจะเร็วกว่าเล็กน้อย แต่นี่จะเร็วกว่าวิธี O (N * M) ของคุณอย่างมากมาย

หากคุณต้องการรวมเหล่านี้คุณสามารถสร้างวิธีการที่มีข้างต้นแล้วคำสั่งส่งคืน:

return !firstNotSecond.Any() && !secondNotFirst.Any();

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

ตัวอย่างเช่นกับรายชื่อ[1, 2, 2, 2, 3]และ[1]ที่ "องค์ประกอบใน List1 แต่ไม่ List2" [2, 2, 2, 3]ผลในรหัสเดิมจะ [2, 3]ด้วยรหัสของฉันมันก็จะเป็น ในหลายกรณีที่จะไม่มีปัญหา แต่ควรรู้ไว้


8
นี่เป็นผลงานที่ยอดเยี่ยมมาก! ขอบคุณสำหรับคำตอบนี้
แฟรงค์

2
ฉันสงสัยว่ามีสองรายการใหญ่มันมีประโยชน์ในการจัดเรียงก่อนเปรียบเทียบหรือไม่ หรือภายในยกเว้นเมธอดส่วนขยายรายการที่ส่งผ่านถูกเรียงลำดับแล้ว
Larry

9
@ Larry: มันไม่ได้เรียง; มันสร้างชุดแฮช
Jon Skeet

2
@PranavSingh: มันจะทำงานเพื่ออะไรก็ตามที่มีความเท่าเทียมกันที่เหมาะสม - ดังนั้นหากการแทนที่ประเภทที่กำหนดเองของคุณEquals(object)และ / หรือนำไปปฏิบัติIEquatable<T>จะต้องใช้ได้
Jon Skeet

2
@ k2ibegin: ใช้การเปรียบเทียบความเท่าเทียมกันเริ่มต้นซึ่งจะใช้การIEquatable<T>ดำเนินการหรือobject.Equals(object)วิธีการ ดูเหมือนคุณควรสร้างคำถามใหม่ด้วยตัวอย่างที่ทำซ้ำได้น้อยที่สุด - เราไม่สามารถวิเคราะห์สิ่งต่าง ๆ ในความคิดเห็นได้
Jon Skeet

40

มีประสิทธิภาพมากขึ้นจะใช้Enumerable.Except:

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

วิธีการนี้จะดำเนินการโดยใช้การดำเนินการรอการตัดบัญชี นั่นหมายความว่าคุณสามารถเขียนเช่น:

var first10 = inListButNotInList2.Take(10);

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


1
อืมมม ไม่ได้รอการตัดบัญชีค่อนข้าง ฉันจะบอกว่ารอการตัดบัญชีบางส่วน ความสมบูรณ์Set<T>ถูกสร้างขึ้นจากลำดับที่สอง (เช่นมันวนซ้ำทั้งหมดและเก็บไว้) จากนั้นรายการที่สามารถเพิ่มจากลำดับแรกจะได้รับผล
อะไรต่อมิอะไร

2
@spender นั่นก็เหมือนกับการบอกว่าการดำเนินการของWhereถูกเลื่อนออกไปบางส่วนเพราะในlist.Where(x => x.Id == 5)ค่าของตัวเลข5จะถูกเก็บไว้ที่จุดเริ่มต้นแทนที่จะดำเนินการอย่างเกียจคร้าน
jwg

27

Enumerable.SequenceEqual วิธีการ

กำหนดว่าสองลำดับจะเท่ากันตาม Comparer Comparer MS.Docs

Enumerable.SequenceEqual(list1, list2);

ใช้ได้กับข้อมูลทุกประเภท หากคุณต้องการใช้มันกับวัตถุที่กำหนดเองคุณต้องนำไปใช้IEqualityComparer

กำหนดวิธีการเพื่อสนับสนุนการเปรียบเทียบวัตถุเพื่อความเท่าเทียมกัน

IEqualityComparer Interface

กำหนดวิธีการเพื่อสนับสนุนการเปรียบเทียบวัตถุเพื่อความเท่าเทียมกัน MS.Docs สำหรับ IEqualityComparer


นี่ควรเป็นคำตอบที่ยอมรับได้ คำถามไม่ได้เกี่ยวกับ SETS แต่เกี่ยวกับ LISTS ซึ่งอาจมีองค์ประกอบที่ซ้ำซ้อน
Adrian Nasui

3
ฉันไม่เห็นวิธีนี้อาจเป็นคำตอบที่ได้รับผลจากการเป็นที่เรียบง่ายSequenceEqual boolOP ต้องการรายการผลลัพธ์สองรายการ - และอธิบายสิ่งที่พวกเขาต้องการในแง่ของการปฏิบัติการที่กำหนด: "รายการที่แสดงในรายการแรก แต่ไม่ใช่ในรายการที่สอง" ไม่มีข้อบ่งชี้ว่าการสั่งซื้อมีความเกี่ยวข้องในขณะที่ SequenceEqual ไม่ถือว่าการสั่งซื้อนั้นมีความเกี่ยวข้อง ดูเหมือนว่าจะตอบคำถามที่แตกต่างอย่างสิ้นเชิง
Jon Skeet

ใช่ถูกต้องดูเหมือนว่าฉันตอบคำถามนี้เร็วเกินไปและไม่ได้ดูที่ส่วนที่สองของคำขอ ... เหมือนกับความคิดเห็นสองข้อแรก ...
miguelmpn

9

หากคุณต้องการให้ผลลัพธ์ไม่ตรงตามตัวพิมพ์ใหญ่ -เล็กสิ่งต่อไปนี้จะใช้ได้:

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondจะมีb1.dll

secondNotFirstจะมีb2.dll


5

ไม่ใช่สำหรับปัญหานี้ แต่นี่คือรหัสเพื่อเปรียบเทียบรายการที่เท่ากันและไม่ใช่! วัตถุที่เหมือนกัน:

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}

1
นี่คือสิ่งที่คุณต้องการเพื่อเปรียบเทียบประเภทข้อมูลที่กำหนดเอง จากนั้นใช้Except
ซิงห์

คุณสามารถทำได้ดีกว่าด้วยประเภทที่เรียงลำดับได้ สิ่งนี้จะทำงานใน O (n ^ 2) ในขณะที่คุณสามารถทำ O (nlogn)
yuvalm2

3

ลองด้วยวิธีนี้:

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

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

3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

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

การใช้งาน:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

ไม่ว่าComponentชั้นเรียนจะเป็นแบบใดวิธีการที่แสดงไว้ที่นี่Carควรนำไปใช้งานเกือบจะเหมือนกันทุกประการ

มันสำคัญมากที่จะต้องทราบว่าเราเขียน GetHashCode อย่างไร เพื่อให้ถูกต้องใช้IEquatable, EqualsและGetHashCode ต้องดำเนินการเกี่ยวกับคุณสมบัติของอินสแตนซ์ในทางที่เข้ากันได้อย่างมีเหตุผล

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

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


1

ฉันใช้โค้ดนี้เพื่อเปรียบเทียบสองรายการที่มีล้านเรคคอร์ด

วิธีนี้จะใช้เวลาไม่นาน

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }

0

หากต้องการผลลัพธ์แบบรวมเท่านั้นสิ่งนี้จะใช้ได้เช่นกัน:

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

โดยที่ T คือประเภทของรายการองค์ประกอบ


-1

อาจเป็นเรื่องตลก แต่ใช้ได้สำหรับฉัน

string.Join ("", List1)! = string.Join ("", List2)


ตามที่เขียนไว้ที่นี่จะไม่สามารถใช้ได้กับ List <string> หรือ List <int> เช่นสองรายการ 11; 2; 3 และ 1; 12; 3 จะเหมือนกันเนื่องจากคุณไม่ได้เข้าร่วมกับสตริง ตัวคั่นที่ไม่ซ้ำกันซึ่งไม่ใช่รายการที่เป็นไปได้ในรายการ นอกจากนั้นการต่อสตริงสำหรับรายการที่มีรายการจำนวนมากน่าจะเป็นตัวฆ่าประสิทธิภาพ
SwissCoder

@SwissCoder: คุณผิดนี่ไม่ใช่นักฆ่า performacne สำหรับสตริง หากคุณมีสองรายการที่มี 50.000 สตริง (แต่ละความยาว 3) อัลกอริทึมนี้ต้องการ 3 ms ในเครื่องของฉัน คำตอบที่ได้รับการยอมรับต้องการ 7. ฉันคิดว่าเคล็ดลับคือ Jibz ต้องการเพียงการเปรียบเทียบสตริงเดียว แน่นอนว่าเขาต้องเพิ่มตัวคั่นพิเศษ
user1027167

@ user1027167: ฉันไม่ได้พูดถึงการเปรียบเทียบสตริงโดยตรง (เพราะนี่ไม่ใช่คำถาม) การเรียกใช้เมธอด. ToString () ของวัตถุทั้งหมดในรายการที่มีวัตถุ 50,000 รายการสามารถสร้างสตริงขนาดใหญ่ขึ้นอยู่กับวิธีการนำไปใช้ ฉันไม่คิดว่าเป็นวิธีที่จะไป ถ้าอย่างนั้นมันก็เสี่ยงที่จะพึ่งพาตัวละครหรือสตริงว่า "ไม่เหมือนใคร" รหัสนั้นจะไม่สามารถนำมาใช้ซ้ำได้
SwissCoder

ตกลงนั่นเป็นความจริง ผู้ถามถามหาวิธีที่รวดเร็วที่สุดโดยไม่ให้ประเภทข้อมูลของรายการของเขา อาจเป็นคำตอบนี้เป็นวิธีที่เร็วที่สุดสำหรับกรณีการใช้งานของผู้ถาม
user1027167

-3

ฉันคิดว่านี่เป็นวิธีที่ง่ายและสะดวกในการเปรียบเทียบรายการสองรายการต่อองค์ประกอบ

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)

3
นี่คือคำถาม C # และคุณยังไม่ได้ให้รหัส C #
Wai Ha Lee

1
บางทีคุณสามารถลบคำตอบนี้และย้ายไปที่ (ตัวอย่าง) ฉันจะเปรียบเทียบสองรายการในหลามและย้อนกลับได้อย่างไร
Wai Ha Lee

-4

นี่คือทางออกที่ดีที่สุดที่คุณจะพบ

var list3 = list1.Where(l => list2.ToList().Contains(l));

1
นี้เป็นจริงที่เลวร้ายมากเพราะมันจะสร้างใหม่สำหรับแต่ละองค์ประกอบในList<T> list1นอกจากนี้ยังมีผลที่เรียกว่าเมื่อมันไม่ได้เป็นlist3 List<T>
Wai Ha Lee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.