ความแตกต่างระหว่างสองรายการ


118

ฉันมีรายการทั่วไปสองรายการที่เต็มไปด้วย CustomsObjects

ฉันต้องการดึงความแตกต่างระหว่างสองรายการ (รายการที่อยู่ในรายการแรกโดยไม่มีรายการในรายการที่สอง) ในรายการที่สาม

ฉันคิดว่าการใช้.Except()เป็นความคิดที่ดี แต่ฉันไม่เห็นวิธีใช้นี้ .. ช่วยด้วย!

c# 

คำตอบ:


237

การใช้Exceptเป็นวิธีที่ถูกต้อง หากประเภทของคุณแทนที่EqualsและGetHashCodeหรือคุณสนใจเฉพาะความเท่าเทียมกันของประเภทการอ้างอิงเท่านั้น (กล่าวคือการอ้างอิงสองรายการจะ "เท่ากัน" หากอ้างอิงถึงวัตถุเดียวกันทั้งหมดเท่านั้น) คุณสามารถใช้:

var list3 = list1.Except(list2).ToList();

หากคุณต้องการที่จะแสดงความคิดที่กำหนดเองของความเท่าเทียมกันเช่นโดยใช้ ID, IEqualityComparer<T>คุณจะต้องดำเนินการ ตัวอย่างเช่น:

public class IdComparer : IEqualityComparer<CustomObject>
{
    public int GetHashCode(CustomObject co)
    {
        if (co == null)
        {
            return 0;
        }
        return co.Id.GetHashCode();
    }

    public bool Equals(CustomObject x1, CustomObject x2)
    {
        if (object.ReferenceEquals(x1, x2))
        {
            return true;
        }
        if (object.ReferenceEquals(x1, null) ||
            object.ReferenceEquals(x2, null))
        {
            return false;
        }
        return x1.Id == x2.Id;
    }
}

จากนั้นใช้:

var list3 = list1.Except(list2, new IdComparer()).ToList();

โปรดทราบว่าสิ่งนี้จะลบองค์ประกอบที่ซ้ำกัน หากคุณต้องการเก็บรักษารายการที่ซ้ำกันไว้อาจเป็นเรื่องง่ายที่สุดในการสร้างชุดจากlist2และใช้สิ่งต่างๆเช่น:

var list3 = list1.Where(x => !set2.Contains(x)).ToList();

18
นอกจากนี้ฉันยังเพิ่มว่าExceptเป็นการดำเนินการSetจากนั้นรายการผลลัพธ์จะมีค่าที่แตกต่างกันเช่น{'A','A','B','C'}.Except({'B','C'}) ผลตอบแทน{'A'}
digEmA ถึง

4
ฉันมีสิ่งนี้: {'A', 'A', 'B', 'C'} ยกเว้น ({'B', 'C'}) คืนค่า {'A'} ซึ่งได้ผล ... อย่างไรก็ตาม {'B' , 'C'} ยกเว้น ({'A', 'B', 'C'}) ไม่ส่งคืน {'A'}
MrLister

2
ความแตกต่างของชุดของสองชุดถูกกำหนดให้เป็นสมาชิกของชุดแรกที่ไม่ปรากฏในชุดที่สอง (อ้างถึง MS) ดังนั้น {B, C} ยกเว้น ({A, B, C}) ไม่ควรคืนค่าใด ๆ เนื่องจาก B และ C อยู่ในชุดที่สอง ไม่ใช่ข้อบกพร่อง แต่จะทำอย่างไรกับนิยามทางคณิตศาสตร์ของฟังก์ชัน
Steve Hibbert

ดังนั้น @JonSkeet ถ้าฉันต้องการเปรียบเทียบสองรายการบนพื้นฐานของการสมมติว่าคุณสมบัติ 2 อย่างฉันสามารถเขียนด้วยวิธีการเปรียบเทียบและรับรายการที่ไม่เท่ากันได้ใช่ไหม
Ehsan Sajjad

1
@NetMage: OP ระบุว่าพวกเขาต้องการ "รายการที่อยู่ในรายการแรกโดยไม่มีรายการในรายการที่สอง" ซึ่งดูเหมือนจะแตกต่างสำหรับฉัน หากรายการแรกมี {5, 5, 5, 5, 1} และรายการที่สองมี {5} จะมีเพียง 1 รายการเท่านั้นที่อยู่ในรายการแรก แต่ไม่ใช่รายการที่สอง
Jon Skeet

74

คุณสามารถทำสิ่งนี้:

var result = customlist.Where(p => !otherlist.Any(l => p.someproperty == l.someproperty));

มันช่วยฉันได้หนึ่งวง foreach
alice7

12
"l.someproperty" หนึ่งในนั้นควรเป็น "p.someproperty" ไม่ใช่หรือ
Manos Dilaverakis

6
สิ่งนี้สามารถทำให้ง่ายขึ้นด้วยรายการที่กำหนดเองโดยที่ (p => otherlist.Any (l => p.someproperty! = l.someproperty));
Dhanuka777

@ Dhanuka777 นั้นไม่ถูกต้อง Anyควรจะเป็นAllถ้าคุณต้องการที่จะทำเช่นนั้น เนื่องจากรายการที่สองอาจมีรายการจากรายการแรก แต่ถ้าไม่ใช่รายการแรกที่ตรวจสอบรายการนั้นจะได้รับจริงp.someproperty != l.somepropertyทันที ส่งผลให้มีการส่งคืนรายการที่มีอยู่ในทั้งสองรายการ สร้างความอับอายให้กับ 6 คนที่โหวตเรื่องนี้
Murphybro2

26

ฉันคิดว่าสิ่งสำคัญที่ต้องเน้น - การใช้วิธียกเว้นจะส่งคืนสินค้าที่อยู่ในรายการแรกโดยไม่มีรายการในรายการที่สองเท่านั้น จะไม่ส่งคืนองค์ประกอบเหล่านั้นในวินาทีที่ไม่ปรากฏในครั้งแรก

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2).ToList(); //list3 contains only 1, 2

แต่ถ้าคุณต้องการความแตกต่างที่แท้จริงระหว่างสองรายการ:

รายการที่อยู่ในรายการแรกโดยไม่มีรายการในรายการที่สองและรายการที่อยู่ในรายการที่สองโดยไม่มีรายการในรายการแรก

คุณต้องใช้ยกเว้นสองครั้ง:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list3 = list1.Except(list2); //list3 contains only 1, 2
var list4 = list2.Except(list1); //list4 contains only 6, 7
var resultList = list3.Concat(list4).ToList(); //resultList contains 1, 2, 6, 7

หรือคุณสามารถใช้วิธีSymmetricExceptWithของ HashSet แต่มันเปลี่ยนชุดที่เรียกว่า:

var list1 = new List<int> { 1, 2, 3, 4, 5};
var list2 = new List<int> { 3, 4, 5, 6, 7 };

var list1Set = list1.ToHashSet(); //.net framework 4.7.2 and .net core 2.0 and above otherwise new HashSet(list1)
list1Set.SymmetricExceptWith(list2);
var resultList = list1Set.ToList(); //resultList contains 1, 2, 6, 7

นั่นเป็นประโยชน์ โปรดทราบว่า OP ทำให้ชัดเจนว่าพวกเขาไม่ต้องการความแตกต่างทั้งหมด: "ฉันต้องการดึงความแตกต่างระหว่างสองรายการนั้น ( รายการที่อยู่ในรายการแรกโดยไม่มีรายการในรายการที่สอง )"
rsenna

10
var third = first.Except(second);

(คุณสามารถโทรToList()ตามExcept()ได้หากคุณไม่ชอบอ้างอิงคอลเลกชันที่ขี้เกียจ)

Except()วิธีการเปรียบเทียบค่าใช้ Comparer เริ่มต้นถ้าค่าที่ถูกเปรียบเทียบเป็นประเภทฐานข้อมูลเช่นint, string, decimalฯลฯ

มิฉะนั้นจะทำการเปรียบเทียบโดยใช้ที่อยู่อ็อบเจ็กต์ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ ... ในกรณีนั้นให้ใช้อ็อบเจ็กต์แบบกำหนดเองของคุณIComparable(หรือใช้แบบกำหนดเองIEqualityComparerและส่งต่อไปยังExcept()เมธอด)


3
var list3 = list1.Where(x => !list2.Any(z => z.Id == x.Id)).ToList();

หมายเหตุ: list3จะมีรายการหรือวัตถุที่ไม่อยู่ในทั้งสองรายการ หมายเหตุ: ToList()ไม่ใช่toList()


1

เนื่องจากวิธีการยกเว้นส่วนขยายทำงานบน IEumerables สองรายการสำหรับฉันแล้วดูเหมือนว่าจะเป็นการดำเนินการ O (n ^ 2) หากประสิทธิภาพเป็นปัญหา (หากบอกว่ารายการของคุณมีขนาดใหญ่) ฉันขอแนะนำให้สร้าง HashSet จาก list1 และใช้เมธอดยกเว้นของ HashSet


9
Enumerable.Exceptใช้ภายในHashSetหรือสิ่งที่คล้ายกัน แน่นอนว่ามันไม่ได้ใช้อัลกอริทึม O (n ^ 2) ที่ไร้เดียงสา
Jim Mischel

@ Jim Mischel: คุณพูดถูกแล้ว Enumerable ยกเว้นใช้โครงสร้างข้อมูลชุดภายในและเพิ่มรายการจาก IEnumerables ทั้งสองลงใน Set หวังว่าเอกสารจะพูดอะไรเกี่ยวกับเรื่องนั้น
foson

1

นี่คือทางออกของฉัน:

    List<String> list1 = new List<String>();

    List<String> list2 = new List<String>();

    List<String> exceptValue = new List<String>();

foreach(String L1 in List1) 
{
    if(!List2.Contains(L1)
    {
         exceptValue.Add(L1);
    }
}
foreach(String L2 in List2) 
{
    if(!List1.Contains(L2)
    {
         exceptValue.Add(L2);
    }
}

-1

สายไปหน่อย แต่นี่เป็นวิธีแก้ปัญหาสำหรับฉัน

 var myBaseProperty = (typeof(BaseClass)).GetProperties();//get base code properties
                    var allProperty = entity.GetProperties()[0].DeclaringType.GetProperties();//get derived class property plus base code as it is derived from it
                    var declaredClassProperties = allProperty.Where(x => !myBaseProperty.Any(l => l.Name == x.Name)).ToList();//get the difference

ในโค้ดที่กล่าวถึงข้างต้นฉันได้รับความแตกต่างของคุณสมบัติระหว่างคลาสพื้นฐานและรายการคลาสที่ได้รับ


-1
var resultList = checklist.Where(p => myList.All(l => p.value != l.value)).ToList();

หมายเหตุ: Result List คือรายการที่มีอยู่ในรายการตรวจสอบ แต่ไม่มีอยู่ใน myList
Teezy7

-2
List<ObjectC> _list_DF_BW_ANB = new List<ObjectC>();    
List<ObjectA> _listA = new List<ObjectA>();
List<ObjectB> _listB = new List<ObjectB>();

foreach (var itemB in _listB )
{     
    var flat = 0;
    foreach(var itemA in _listA )
    {
        if(itemA.ProductId==itemB.ProductId)
        {
            flat = 1;
            break;
        }
    }
    if (flat == 0)
    {
        _list_DF_BW_ANB.Add(itemB);
    }
}

6 ปีหลังจากคำตอบที่ได้รับการยอมรับและไม่มีอะไรเพิ่มมูลค่าใหม่
Mitch Wheat

-3

หากทั้งสองรายการของคุณใช้อินเทอร์เฟซ IEnumerable คุณสามารถทำได้โดยใช้ LINQ

list3 = list1.where(i => !list2.contains(i));

6
และหากแต่ละรายการของคุณมีรายการเป็นล้านรายการคุณจะต้องรอเป็นเวลานาน ใช้IEnumerable.Exceptแทน
Jim Mischel

3
อ๋อ ฉันมีเพียง 450 รายการและฉันยังคงรอ ระวังการใช้สิ่งนี้
Marnee KG7SIO

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