LINQ: ค่าที่แตกต่างกัน


137

ฉันมีชุดรายการต่อไปนี้จาก XML:

id           category

5            1
5            3
5            4
5            3
5            3

ฉันต้องการรายชื่อที่แตกต่างกันของรายการเหล่านี้:

5            1
5            3
5            4

ฉันจะแยกประเภทและรหัสใน LINQ ได้อย่างไร

คำตอบ:


223

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

var query = doc.Elements("whatever")
               .Select(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Distinct();

หากคุณกำลังพยายามรับชุดค่าที่แตกต่างกันของประเภท "ใหญ่กว่า" แต่ดูเฉพาะคุณสมบัติบางส่วนสำหรับด้านความแตกต่างคุณอาจต้องการDistinctByนำไปใช้ในMoreLINQในDistinctBy.cs:

 public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
     this IEnumerable<TSource> source,
     Func<TSource, TKey> keySelector,
     IEqualityComparer<TKey> comparer)
 {
     HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
     foreach (TSource element in source)
     {
         if (knownKeys.Add(keySelector(element)))
         {
             yield return element;
         }
     }
 }

(หากคุณส่งnullเป็นตัวเปรียบเทียบจะใช้ตัวเปรียบเทียบเริ่มต้นสำหรับประเภทคีย์)


"ประเภทใหญ่กว่า" คุณอาจหมายความว่าฉันยังต้องการคุณสมบัติทั้งหมดในผลลัพธ์แม้ว่าฉันต้องการเปรียบเทียบคุณสมบัติเพียงเล็กน้อยเพื่อระบุความแตกต่าง?
Red Pea

@TheRedPea: ใช่แน่นอน
Jon Skeet


27

นอกเหนือจากคำตอบของ Jon Skeet แล้วคุณยังสามารถใช้กลุ่มตามนิพจน์เพื่อรับกลุ่มที่ไม่ซ้ำกันพร้อมกับจำนวนสำหรับการวนซ้ำแต่ละกลุ่ม:

var query = from e in doc.Elements("whatever")
            group e by new { id = e.Key, val = e.Value } into g
            select new { id = g.Key.id, val = g.Key.val, count = g.Count() };

4
คุณเขียนว่า "นอกเหนือจากคำตอบของ Jon Skeet" ... ฉันไม่รู้ว่าจะเป็นไปได้ไหม ;)
Yehuda Makarov

13

สำหรับใครที่ยังมองหา; นี่เป็นอีกวิธีหนึ่งในการใช้ lambda Comparer ที่กำหนดเอง

public class LambdaComparer<T> : IEqualityComparer<T>
    {
        private readonly Func<T, T, bool> _expression;

        public LambdaComparer(Func<T, T, bool> lambda)
        {
            _expression = lambda;
        }

        public bool Equals(T x, T y)
        {
            return _expression(x, y);
        }

        public int GetHashCode(T obj)
        {
            /*
             If you just return 0 for the hash the Equals comparer will kick in. 
             The underlying evaluation checks the hash and then short circuits the evaluation if it is false.
             Otherwise, it checks the Equals. If you force the hash to be true (by assuming 0 for both objects), 
             you will always fall through to the Equals check which is what we are always going for.
            */
            return 0;
        }
    }

จากนั้นคุณสามารถสร้างส่วนขยายสำหรับ linq Distinct ที่สามารถใช้แลมบ์ดาได้

   public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list,  Func<T, T, bool> lambda)
        {
            return list.Distinct(new LambdaComparer<T>(lambda));
        }  

การใช้งาน:

var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);

เมื่อมองไปที่แหล่งอ้างอิง Distinct ใช้ชุดแฮชเพื่อจัดเก็บองค์ประกอบที่ให้ผลแล้ว ส่งคืนรหัสแฮชเดียวกันเสมอหมายความว่าทุกองค์ประกอบที่ส่งคืนก่อนหน้านี้จะได้รับการตรวจสอบทุกครั้ง รหัสแฮชที่มีประสิทธิภาพมากขึ้นจะทำให้สิ่งต่าง ๆ เร็วขึ้นเพราะจะเปรียบเทียบกับองค์ประกอบในที่เก็บแฮชเดียวกันเท่านั้น ศูนย์เป็นค่าเริ่มต้นที่สมเหตุสมผล แต่อาจคุ้มค่าที่จะสนับสนุนแลมบ์ดาตัวที่สองสำหรับรหัสแฮช
Darryl

จุดดี! ฉันจะลองแก้ไขเมื่อมีเวลาถ้าคุณกำลังทำงานในโดเมนนี้ในขณะนี้อย่าลังเลที่จะแก้ไข
Ricky G

8

ฉันตอบช้าไปหน่อย แต่คุณอาจต้องทำเช่นนี้หากคุณต้องการองค์ประกอบทั้งหมดไม่ใช่เฉพาะค่าที่คุณต้องการจัดกลุ่มตาม:

var query = doc.Elements("whatever")
               .GroupBy(element => new {
                             id = (int) element.Attribute("id"),
                             category = (int) element.Attribute("cat") })
               .Select(e => e.First());

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


4
// First Get DataTable as dt
// DataRowComparer Compare columns numbers in each row & data in each row

IEnumerable<DataRow> Distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default);

foreach (DataRow row in Distinct)
{
    Console.WriteLine("{0,-15} {1,-15}",
        row.Field<int>(0),
        row.Field<string>(1)); 
}

1

เนื่องจากเรากำลังพูดถึงการมีทุกองค์ประกอบในครั้งเดียว "ชุด" จึงเหมาะสมกับฉันมากกว่า

ตัวอย่างกับคลาสและ IEqualityComparer ดำเนินการ:

 public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Product(int x, string y)
        {
            Id = x;
            Name = y;
        }
    }

    public class ProductCompare : IEqualityComparer<Product>
    {
        public bool Equals(Product x, Product y)
        {  //Check whether the compared objects reference the same data.
            if (Object.ReferenceEquals(x, y)) return true;

            //Check whether any of the compared objects is null.
            if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
                return false;

            //Check whether the products' properties are equal.
            return x.Id == y.Id && x.Name == y.Name;
        }
        public int GetHashCode(Product product)
        {
            //Check whether the object is null
            if (Object.ReferenceEquals(product, null)) return 0;

            //Get hash code for the Name field if it is not null.
            int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode();

            //Get hash code for the Code field.
            int hashProductCode = product.Id.GetHashCode();

            //Calculate the hash code for the product.
            return hashProductName ^ hashProductCode;
        }
    }

ตอนนี้

List<Product> originalList = new List<Product> {new Product(1, "ad"), new Product(1, "ad")};
var setList = new HashSet<Product>(originalList, new ProductCompare()).ToList();

setList จะมีองค์ประกอบที่เป็นเอกลักษณ์

ฉันคิดถึงสิ่งนี้ในขณะที่จัดการกับ.Except()สิ่งที่ส่งคืนผลต่างเซต

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