LINQ: Unterschiedliche Werte

136

Ich habe das folgende Element aus einem XML festgelegt:

id           category

5            1
5            3
5            4
5            3
5            3

Ich benötige eine eindeutige Liste dieser Elemente:

5            1
5            3
5            4

Wie kann ich in LINQ auch für Kategorie UND ID unterscheiden?

balint
quelle

Antworten:

221

Versuchen Sie, sich durch mehr als ein Feld zu unterscheiden? Wenn ja, verwenden Sie einfach einen anonymen Typ und den Operator Distinct und es sollte in Ordnung sein:

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

Wenn Sie versuchen, eine bestimmte Menge von Werten eines "größeren" Typs zu erhalten, aber nur eine Teilmenge von Eigenschaften für den Unterscheidungsaspekt betrachten, möchten Sie wahrscheinlich, DistinctBywie in MoreLINQ implementiert in 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;
         }
     }
 }

(Wenn Sie nullals Vergleicher übergeben, wird der Standardvergleicher für den Schlüsseltyp verwendet.)

Jon Skeet
quelle
Oh, mit "größerer Typ" meinen Sie vielleicht, dass ich immer noch alle Eigenschaften im Ergebnis haben möchte, obwohl ich nur einige Eigenschaften vergleichen möchte, um die Unterscheidbarkeit zu bestimmen?
Die rote Erbse
@ TheRedPea: Ja genau.
Jon Skeet
27

Zusätzlich zu Jon Skeets Antwort können Sie die Gruppe auch nach Ausdrücken verwenden, um die eindeutigen Gruppen mit einer Anzahl für jede Gruppeniteration zu ermitteln:

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() };
James Alexander
quelle
4
Sie haben "zusätzlich zu Jon Skeets Antwort" geschrieben ... Ich weiß nicht, ob so etwas möglich ist. ;)
Yehuda Makarov
13

Für jeden, der noch sucht; Hier ist eine andere Möglichkeit, einen benutzerdefinierten Lambda-Vergleicher zu implementieren.

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;
        }
    }

Sie können dann eine Erweiterung für den linq Distinct erstellen, die Lambdas aufnehmen kann

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

Verwendung:

var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);
Ricky G.
quelle
In Bezug auf die Referenzquelle verwendet Distinct einen Hash-Satz, um bereits erbrachte Elemente zu speichern. Immer den gleichen Hash-Code zurückzugeben bedeutet, dass jedes zuvor zurückgegebene Element jedes Mal überprüft wird. Ein robusterer Hash-Code würde die Dinge beschleunigen, da er nur mit Elementen im selben Hash-Bucket verglichen werden würde. Null ist eine vernünftige Standardeinstellung, aber es könnte sich lohnen, ein zweites Lambda für den Hash-Code zu unterstützen.
Darryl
Guter Punkt! Ich werde versuchen zu bearbeiten, wenn ich Zeit habe. Wenn Sie im Moment in dieser Domain arbeiten, können Sie gerne bearbeiten
Ricky G
8

Ich bin etwas spät bei der Antwort, aber Sie können dies tun, wenn Sie das gesamte Element und nicht nur die Werte, nach denen Sie gruppieren möchten, möchten:

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

Auf diese Weise erhalten Sie das erste vollständige Element, das Ihrer Gruppe nach Auswahl entspricht, ähnlich wie im zweiten Beispiel von Jon Skeets mit DistinctBy, jedoch ohne den IEqualityComparer-Vergleicher zu implementieren. DistinctBy wird höchstwahrscheinlich schneller sein, aber die obige Lösung wird weniger Code beinhalten, wenn die Leistung kein Problem darstellt.

Olle Johansson
quelle
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)); 
}
Mohamed Elsayed
quelle
0

Da es darum geht, jedes Element genau einmal zu haben, ist eine "Menge" für mich sinnvoller.

Beispiel mit implementierten Klassen und 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;
        }
    }

Jetzt

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

setList wird einzigartige Elemente haben

Ich dachte daran, während ich mich damit befasste, .Except()was einen Satzunterschied ergibt

Aditya AVS
quelle