ใช้ LINQ เพื่อรับรายการในหนึ่งรายการ <> ที่ไม่ได้อยู่ในรายการอื่น <>


526

ฉันจะสมมติว่ามีการสืบค้น LINQ แบบง่าย ๆ เพื่อทำสิ่งนี้ฉันไม่แน่ใจจริงๆ

รับรหัสชิ้นนี้:

class Program
{
    static void Main(string[] args)
    {
        List<Person> peopleList1 = new List<Person>();
        peopleList1.Add(new Person() { ID = 1 });
        peopleList1.Add(new Person() { ID = 2 });
        peopleList1.Add(new Person() { ID = 3 });

        List<Person> peopleList2 = new List<Person>();
        peopleList2.Add(new Person() { ID = 1 });
        peopleList2.Add(new Person() { ID = 2 });
        peopleList2.Add(new Person() { ID = 3 });
        peopleList2.Add(new Person() { ID = 4 });
        peopleList2.Add(new Person() { ID = 5 });
    }
}

class Person
{
    public int ID { get; set; }
}

ฉันต้องการทำแบบสอบถาม LINQ เพื่อให้ทุกคนpeopleList2ที่ไม่ได้เข้าpeopleList1ร่วม

ตัวอย่างนี้ควรให้ฉันสองคน (ID = 4 & ID = 5)


3
อาจเป็นความคิดที่ดีที่จะสร้าง ID แบบอ่านอย่างเดียวเนื่องจากตัวตนของวัตถุไม่ควรเปลี่ยนแปลงตลอดเวลา นอกเสียจากว่าแน่นอนกรอบการทดสอบหรือ ORM ของคุณต้องการให้มันไม่แน่นอน
CodesInChaos

2
เราสามารถเรียกสิ่งนี้ว่า "ซ้าย (หรือขวา) ไม่รวมการเข้าร่วม" ตามแผนภาพนี้?
ถั่วแดง

คำตอบ:


912

สิ่งนี้สามารถแก้ไขได้โดยใช้นิพจน์ LINQ ต่อไปนี้:

var result = peopleList2.Where(p => !peopleList1.Any(p2 => p2.ID == p.ID));

อีกทางเลือกหนึ่งในการแสดงสิ่งนี้ผ่านทาง LINQ ซึ่งนักพัฒนาบางคนพบว่าสามารถอ่านได้มากขึ้น:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

คำเตือน:ตามที่ระบุไว้ในความคิดเห็นเหล่านี้วิธีอาณัติO (n * เมตร)การดำเนินงาน อาจเป็นเรื่องที่ดี แต่สามารถแนะนำปัญหาด้านประสิทธิภาพและโดยเฉพาะอย่างยิ่งหากชุดข้อมูลมีขนาดค่อนข้างใหญ่ หากสิ่งนี้ไม่ตรงตามข้อกำหนดด้านประสิทธิภาพของคุณคุณอาจต้องประเมินตัวเลือกอื่น ๆ เนื่องจากข้อกำหนดที่ระบุไว้ใช้สำหรับการแก้ปัญหาใน LINQ อย่างไรก็ตามตัวเลือกเหล่านั้นจึงไม่ได้รับการสำรวจที่นี่ เช่นเคยประเมินวิธีการใด ๆ ที่ขัดกับข้อกำหนดด้านประสิทธิภาพที่โครงการของคุณอาจมี


34
คุณทราบหรือไม่ว่านั่นเป็นคำตอบ O (n * m) สำหรับปัญหาที่สามารถแก้ไขได้อย่างง่ายดายในเวลา O (n + m)
Niki

32
@nikie ผู้ปฏิบัติการได้ขอวิธีแก้ปัญหาที่ใช้ Linq บางทีเขาพยายามเรียนรู้ Linq หากคำถามเป็นไปอย่างมีประสิทธิภาพที่สุดคำถามของฉันก็คงไม่เหมือนเดิม
Klaus Byskov Pedersen

46
@nikie อยากแบ่งปันวิธีแก้ปัญหาง่ายๆของคุณ?
รูบิโอ

18
สิ่งนี้เทียบเท่าและฉันค้นหาได้ง่ายขึ้น: var result = peopleList2.Where (p => peopleList1.All (p2 => p2.ID! = p.ID));
AntonK

28
@Menol - อาจไม่ยุติธรรมเลยที่จะวิจารณ์คนที่ตอบคำถามอย่างถูกต้อง ผู้คนไม่จำเป็นต้องคาดการณ์ถึงวิธีการและบริบททั้งหมดที่ผู้คนในอนาคตอาจสะดุดกับคำตอบ ในความเป็นจริงคุณควรกำกับให้กับ nikie - ใครใช้เวลาในการระบุว่าพวกเขารู้ทางเลือกอื่นโดยไม่ต้องให้
Chris Rogers

396

หากคุณแทนที่ความเท่าเทียมกันของผู้คนแล้วคุณสามารถใช้:

peopleList2.Except(peopleList1)

Exceptควรเร็วกว่าWhere(...Any)ชุดตัวแปรอย่างมีนัยสำคัญเนื่องจากสามารถใส่รายการที่สองลงใน hashtable Where(...Any)มีรันไทม์ของO(peopleList1.Count * peopleList2.Count)ในขณะที่สายพันธุ์ขึ้นอยู่กับHashSet<T>(เกือบ) O(peopleList1.Count + peopleList2.Count)มีรันไทม์ของ

Exceptลบรายการที่ซ้ำกันโดยปริยาย ไม่ควรส่งผลกระทบต่อกรณีของคุณ แต่อาจเป็นปัญหาสำหรับกรณีที่คล้ายกัน

หรือถ้าคุณต้องการรหัสที่รวดเร็ว แต่ไม่ต้องการแทนที่ความเท่าเทียมกัน:

var excludedIDs = new HashSet<int>(peopleList1.Select(p => p.ID));
var result = peopleList2.Where(p => !excludedIDs.Contains(p.ID));

ตัวแปรนี้ไม่ได้ลบรายการที่ซ้ำกัน


สิ่งนี้จะใช้ได้ก็ต่อเมื่อEqualsถูกแทนที่เพื่อเปรียบเทียบ ID
Klaus Byskov Pedersen

34
นั่นเป็นเหตุผลที่ฉันเขียนว่าคุณต้องเอาชนะความเท่าเทียมกัน แต่ฉันได้เพิ่มตัวอย่างที่ใช้งานได้แม้ไม่มีสิ่งนั้น
CodesInChaos

4
มันจะใช้งานได้ถ้า Person เป็น struct ตามที่เป็นอยู่บุคคลดูเหมือนว่าคลาสไม่สมบูรณ์เนื่องจากมีคุณสมบัติที่เรียกว่า "ID" ซึ่งไม่ได้ระบุ - ถ้ามันระบุไว้ดังนั้นจะเท่ากับถูกแทนที่ดังนั้น ID ที่เท่ากันหมายถึงบุคคลที่เท่าเทียมกัน เมื่อแก้ไขข้อผิดพลาดในบุคคลแล้ววิธีการนี้จะดีกว่า (เว้นแต่ว่าข้อผิดพลาดได้รับการแก้ไขโดยเปลี่ยนชื่อ "ID" เป็นอย่างอื่นที่ไม่ทำให้เข้าใจผิดโดยดูเหมือนจะเป็นตัวระบุ)
Jon Hanna

2
นอกจากนี้ยังใช้งานได้ดีหากคุณกำลังพูดถึงรายการสตริง (หรือวัตถุพื้นฐานอื่น ๆ ) ซึ่งเป็นสิ่งที่ฉันค้นหาเมื่อฉันมาที่กระทู้นี้
Dan Korn

@DanKorn เหมือนกันนี่เป็นวิธีที่ง่ายกว่าเมื่อเทียบกับที่ไหนสำหรับการเปรียบเทียบพื้นฐาน int การอ้างอิงวัตถุสตริง
เขาวงกต

73

หรือถ้าคุณต้องการโดยไม่มีการปฏิเสธ:

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));

โดยพื้นฐานแล้วมันบอกว่ารับทั้งหมดจาก peopleList2 โดยที่ id ทั้งหมดใน peopleList1 นั้นแตกต่างจาก id ใน peoplesList2

แตกต่างเล็กน้อยจากคำตอบที่ยอมรับ :)


5
วิธีนี้ (รายการมากกว่า 50,000 รายการ) เร็วกว่าวิธีใดก็ได้!
DaveN

5
อาจเร็วกว่านี้เพียงเพราะขี้เกียจ โปรดทราบว่านี่ยังไม่ได้ทำงานจริงใด ๆ มันไม่ได้จนกว่าคุณจะระบุรายการที่ใช้งานได้จริง (โดยการโทร ToList หรือใช้เป็นส่วนหนึ่งของวง foreach ฯลฯ )
Xtros

32

เนื่องจากโซลูชันทั้งหมดจนถึงปัจจุบันใช้ไวยากรณ์ได้อย่างคล่องนี่คือโซลูชันในไวยากรณ์นิพจน์แบบสอบถามสำหรับผู้ที่สนใจ:

var peopleDifference = 
  from person2 in peopleList2
  where !(
      from person1 in peopleList1 
      select person1.ID
    ).Contains(person2.ID)
  select person2;

ฉันคิดว่ามันแตกต่างจากคำตอบที่ให้ความสนใจแม้บางคนคิดว่าน่าจะเป็นสิ่งที่ไม่ดีสำหรับรายการ ตอนนี้สำหรับตารางที่มี ID ที่จัดทำดัชนีแล้วนี่จะเป็นวิธีที่แน่นอน


ขอบคุณ. คำตอบแรกที่รบกวนจิตใจด้วยไวยากรณ์นิพจน์แบบสอบถาม
ชื่อสามัญ

15

สายไปงานปาร์ตี้ แต่ทางออกที่ดีซึ่งยังรองรับ Linq to SQL คือ

List<string> list1 = new List<string>() { "1", "2", "3" };
List<string> list2 = new List<string>() { "2", "4" };

List<string> inList1ButNotList2 = (from o in list1
                                   join p in list2 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inList2ButNotList1 = (from o in list2
                                   join p in list1 on o equals p into t
                                   from od in t.DefaultIfEmpty()
                                   where od == null
                                   select o).ToList<string>();

List<string> inBoth = (from o in list1
                       join p in list2 on o equals p into t
                       from od in t.DefaultIfEmpty()
                       where od != null
                       select od).ToList<string>();

ความรุ่งโรจน์ถึงhttp://www.dotnet-tricks.com/Tutorial/linq/UXPF181012-SQL-Joins-with-C


12

คำตอบของ Klaus นั้นยอดเยี่ยม แต่ ReSharper จะขอให้คุณ "ลดความซับซ้อนของการแสดงออก LINQ":

var result = peopleList2.Where(p => peopleList1.All(p2 => p2.ID != p.ID));


ควรทราบว่าเคล็ดลับนี้ใช้ไม่ได้หากมีมากกว่าหนึ่งคุณสมบัติที่ผูกกับวัตถุสองรายการ (คิดว่าคีย์ผสม SQL)
Alrekr

Alrekr - หากสิ่งที่คุณหมายถึงการพูดคือ "คุณจะต้องเปรียบเทียบคุณสมบัติมากขึ้นหากคุณสมบัติเพิ่มเติมต้องเปรียบเทียบ" แล้วฉันจะบอกว่าค่อนข้างชัดเจน
Lucas Morgan

8

ส่วนขยายที่นับได้นี้ช่วยให้คุณกำหนดรายการที่จะแยกและฟังก์ชั่นที่ใช้ในการค้นหาคีย์เพื่อใช้ทำการเปรียบเทียบ

public static class EnumerableExtensions
{
    public static IEnumerable<TSource> Exclude<TSource, TKey>(this IEnumerable<TSource> source,
    IEnumerable<TSource> exclude, Func<TSource, TKey> keySelector)
    {
       var excludedSet = new HashSet<TKey>(exclude.Select(keySelector));
       return source.Where(item => !excludedSet.Contains(keySelector(item)));
    }
}

คุณสามารถใช้วิธีนี้

list1.Exclude(list2, i => i.ID);

โดยมีรหัสที่ @BrianT มีฉันจะแปลงให้ใช้รหัสของคุณได้อย่างไร
Nicke

0

นี่คือตัวอย่างการทำงานที่ได้รับทักษะด้านไอทีที่ผู้สมัครงานไม่ได้มีอยู่

//Get a list of skills from the Skill table
IEnumerable<Skill> skillenum = skillrepository.Skill;
//Get a list of skills the candidate has                   
IEnumerable<CandSkill> candskillenum = candskillrepository.CandSkill
       .Where(p => p.Candidate_ID == Candidate_ID);             
//Using the enum lists with LINQ filter out the skills not in the candidate skill list
IEnumerable<Skill> skillenumresult = skillenum.Where(p => !candskillenum.Any(p2 => p2.Skill_ID == p.Skill_ID));
//Assign the selectable list to a viewBag
ViewBag.SelSkills = new SelectList(skillenumresult, "Skill_ID", "Skill_Name", 1);

0

ก่อนดึงรหัสจากการรวบรวมที่สภาพ

List<int> indexes_Yes = this.Contenido.Where(x => x.key == 'TEST').Select(x => x.Id).ToList();

วินาทีให้ใช้ Estament "เปรียบเทียบ" เพื่อเลือกรหัสที่แตกต่างกับการเลือก

List<int> indexes_No = this.Contenido.Where(x => !indexes_Yes.Contains(x.Id)).Select(x => x.Id).ToList();

เห็นได้ชัดว่าคุณสามารถใช้ x.key! = "ทดสอบ" แต่เป็นเพียงตัวอย่างเท่านั้น


0

เมื่อคุณเขียน FuncEqualityComparer สามัญคุณสามารถใช้งานได้ทุกที่

peopleList2.Except(peopleList1, new FuncEqualityComparer<Person>((p, q) => p.ID == q.ID));

public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, T, bool> comparer;
    private readonly Func<T, int> hash;

    public FuncEqualityComparer(Func<T, T, bool> comparer)
    {
        this.comparer = comparer;
        if (typeof(T).GetMethod(nameof(object.GetHashCode)).DeclaringType == typeof(object))
            hash = (_) => 0;
        else
            hash = t => t.GetHashCode(); 
    }

    public bool Equals(T x, T y) => comparer(x, y);
    public int GetHashCode(T obj) => hash(obj);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.