web-dev-qa-db-ja.com

複雑なオブジェクトのGroupBy(例:List <T>)

GroupBy()Count() > 1を使用して、リスト内でクラスの重複インスタンスを見つけようとしています。

クラスは次のようになります。

public class SampleObject
{
    public string Id;
    public IEnumerable<string> Events;
}

そして、これは私がリストをインスタンス化してグループ化する方法です:

public class Program
{
    private static void Main(string[] args)
    {
        var items = new List<SampleObject>()
        {
            new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } },
            new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } }
        };

        var duplicates = items.GroupBy(x => new { Token = x.Id, x.Events })
                         .Where(g => g.Count() > 1)
                         .Select(g => g.Key)
                         .ToList();
    }
}

duplicatesにはアイテムが含まれていません。グループ化を機能させるにはどうすればよいですか?

13
Jake Manet

オブジェクトをGroupByDistinctなどの多くのLINQの演算子で動作させるには、GetHashCodeEqualsを実装するか、カスタム比較器。

あなたの場合、リストとしてプロパティを使用する場合、リストを読み取り専用にしない限り、おそらく比較子が必要です。

この比較器を試してください:

public class SampleObjectComparer : IEqualityComparer<SampleObject>
{
    public bool Equals(SampleObject x, SampleObject y)
    {
        return x.Id == y.Id && x.Events.SequenceEqual(y.Events);
    }

    public int GetHashCode(SampleObject x)
    {
        return x.Id.GetHashCode() ^ x.Events.Aggregate(0, (a, y) => a ^ y.GetHashCode());
    }
}

これで、このコードは機能します。

    var items = new List<SampleObject>()
    {
        new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent"} },
        new SampleObject() { Id = "Id", Events = new List<string>() { "ExampleEvent" } }
    };

    var comparer = new SampleObjectComparer();

    var duplicates = items.GroupBy(x => x, comparer)
                     .Where(g => g.Count() > 1)
                     .Select(g => g.Key)
                     .ToList();
13
Enigmativity

GroupBy()はデフォルトの比較を実行し、リストが等しくないことを検出します。

次のコードを参照してください。

_var eventList1 = new List<string>() { "ExampleEvent" };
var eventList2 = new List<string>() { "ExampleEvent" };

Console.WriteLine(eventList1.GetHashCode());
Console.WriteLine(eventList2.GetHashCode());
Console.WriteLine(eventList1.Equals(eventList2));
_

2つの「等しい」リストですね。ただし、これは次のように出力されます。

_796641852
1064243573
False
_

したがって、それらは等しいとは見なされないため、グループ化されません。

オブジェクトの関連するプロパティを比較するカスタム比較子を提供する必要があります。前に示したように、List<T>.GetHashCode()はリスト内の項目を適切に表していないことに注意してください

そのように行うことができます( 順序を尊重するFooオブジェクトのリストのGood GetHashCode()オーバーライド および 複数のref-typeフィールドのLINQ GroupBy;カスタムEqualityComparer ):

_public class SampleObjectComparer : IEqualityComparer<SampleObject>
{
    public bool Equals(SampleObject a, SampleObject b)
    {
        return a.Id == b.Id 
            && a.Events.SequenceEqual(b.Events);
    }

    public int GetHashCode(SampleObject a)
    {
        int hash = 17;

        hash = hash * 23 + a.Id.GetHashCode();

        foreach (var evt in a.Events)
        {
            hash = hash * 31 + evt.GetHashCode();
        }           

        return hash;
    }
}
_

そして、次のように使用します。

_var eventList1 = new List<string>() { "ExampleEvent" };
var eventList2 = new List<string>() { "ExampleEvent" };

var items = new List<SampleObject>()
{
    new SampleObject() { Id = "Id", Events = eventList1 },
    new SampleObject() { Id = "Id", Events = eventList2 }
};

var duplicates = items.GroupBy(x => x, new SampleObjectComparer())
                 .Where(g => g.Count() > 1)
                 .Select(g => g.Key)
                 .ToList();

Console.WriteLine(duplicates.Count);
_
1
CodeCaster

_List<T>_にはオーバーライドされたEquals + GetHashCodeがないため、GroupByが期待どおりに機能しません。匿名タイプの2つのプロパティの1つがリストを参照し、GroupByが2つのリストを比較する必要がある場合、_Object.RefernceEquals_が使用されます。要素。

カスタム_IEqualityComparer<T>_を提供できます:

_public class IdEventComparer : IEqualityComparer<SampleObject>
{
    public bool Equals(SampleObject x, SampleObject y)
    {
        if (object.ReferenceEquals(x, y)) 
            return true;
        if (x == null || y == null) 
            return false;
        if(x.Id != y.Id) 
            return false;
        if (x.Events == null && y.Events == null)
            return true;
        if (x.Events == null || y.Events == null)
            return false;

        return x.Events.SequenceEqual(y.Events);
    }

    public int GetHashCode(SampleObject obj)
    {
        if(obj == null) return 23;
        unchecked
        {
            int hash = 23;
            hash = (hash * 31) + obj.Id == null ? 31 : obj.Id.GetHashCode();

            if (obj.Events == null) return hash;
            foreach (string item in obj.Events)
            {
                hash = (hash * 31) + (item == null ? 0 : item.GetHashCode());
            }
            return hash;
        }
    }
}
_

次に、GroupByのような多くのLINQメソッドで使用できます。

_var duplicates = items.GroupBy(x => x, new IdEventComparer())
     .Where(g => g.Count() > 1)
     .Select(g => g.Key)
     .ToList();
_
1
Tim Schmelter