J'ai le jeu d'éléments suivant à partir d'un XML:
id category
5 1
5 3
5 4
5 3
5 3
J'ai besoin d'une liste distincte de ces articles:
5 1
5 3
5 4
Comment puis-je distinguer la catégorie ET l'ID aussi dans LINQ?
Essayez-vous d'être distinct par plus d'un domaine? Si c'est le cas, utilisez simplement un type anonyme et l'opérateur Distinct et tout devrait bien se passer:
var query = doc.Elements("whatever")
.Select(element => new {
id = (int) element.Attribute("id"),
category = (int) element.Attribute("cat") })
.Distinct();
Si vous essayez d'obtenir un ensemble distinct de valeurs d'un type "plus grand", mais en ne regardant qu'un sous-ensemble de propriétés pour l'aspect de distinction, vous souhaiterez probablement DistinctBy
tel qu'implémenté dans MoreLINQ dans 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;
}
}
}
(Si vous passez null
comme comparateur, il utilisera le comparateur par défaut pour le type de clé.)
Utilisez simplement la Distinct()
avec votre propre comparateur.
En plus de la réponse de Jon Skeet, vous pouvez également utiliser l'expression par groupe pour obtenir les groupes uniques avec un compte pour les itérations de chaque groupe:
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() };
Pour ceux qui cherchent encore; voici une autre façon de mettre en œuvre un comparateur lambda personnalisé.
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;
}
}
vous pouvez ensuite créer une extension pour le linq Distinct qui peut prendre en lambda
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, T, bool> lambda)
{
return list.Distinct(new LambdaComparer<T>(lambda));
}
tilisation:
var availableItems = list.Distinct((p, p1) => p.Id== p1.Id);
Je suis un peu en retard pour la réponse, mais vous voudrez peut-être faire cela si vous voulez tout l'élément, pas seulement les valeurs que vous voulez grouper par:
var query = doc.Elements("whatever")
.GroupBy(element => new {
id = (int) element.Attribute("id"),
category = (int) element.Attribute("cat") })
.Select(e => e.First());
Cela vous donnera le premier élément entier correspondant à votre groupe par sélection, un peu comme le deuxième exemple de Jon Skeets utilisant DistinctBy, mais sans implémentation de IEqualityComparer Compare. DistinctBy sera probablement plus rapide, mais la solution ci-dessus impliquera moins de code si les performances ne sont pas un problème.
// 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));
}
Puisqu'on parle d'avoir tous les éléments exactement une fois, un "ensemble" a plus de sens pour moi.
Exemple avec des classes et IEqualityComparer implémenté:
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;
}
}
Maintenant
List<Product> originalList = new List<Product> {new Product(1, "ad"), new Product(1, "ad")};
var setList = new HashSet<Product>(originalList, new ProductCompare()).ToList();
setList
aura des éléments uniques
J'y ai pensé tout en traitant de .Except()
qui retourne une différence de set