1
votes

Comparer la liste dans LINQ et renvoyer 2 objets de résultat

J'ai 2 listes: List newCars et List oldCars .

Je dois utiliser la List newCars comme source de vérité et renvoyer les voitures présentes et absentes dans la liste List oldCars .

Comment puis-je faire cela?

Mon approche:

var present = newCars.Where(c => oldCars.All(w => w.Id != c.Id));
var absent = newCars.Where(c => oldCars.All(w => w.Id == c.Id));

Je suis assez nouveau sur LINQ, et je ne suis pas sûr de la logique que j'ai utilisée ci-dessus.

  1. Quelqu'un peut-il m'aider pour que cela fonctionne de manière plus efficace et optimisée?

  2. Cela peut-il être fait en une seule requête et renvoyer 2 ensembles de résultats (sous forme de tuples)?

Je me rends compte qu'exécuter le même code avec des égaux et non des égaux que ci-dessus peut être une approche coûteuse.


6 commentaires

Equi-join et rejoindre gauche .


Si vous avez besoin de deux listes résultantes, utilisez une boucle for / foreach normale. Remplissez les deux en un seul passage.


examinez la méthode Intersect de la classe List. cela renverra des éléments communs dans 2 listes et renverra les résultats dans une nouvelle liste.


Tout d'abord, j'obtiendrais une liste de tous les objets Car qui correspondent à l'aide de LINQ. Ensuite, vous pouvez utiliser List.Contains (item) == false dans une boucle


Ne devrait pas être absent utiliser Any au lieu de All


Votre déclaration du présent et de l'absence semble ambiguë, sur la base de votre code, je suppose que vous voulez diviser la liste des newCars en voitures présentes dans oldCars et en voitures absentes de oldCars , auquel cas vous devriez avoir Any au lieu de All - évidemment oldCars.All ne sera vrai que si oldCars contient une voiture.


4 Réponses :


0
votes

Vous pouvez créer un IEqualityComparer personnalisé :

var present = newCars.Intersect(oldCars, new CarEqualityComparer());
var absent = newCars.Except(oldCars, new CarEqualityComparer());

Et l'utiliser comme ceci:

public class CarEqualityComparer : IEqualityComparer<Car>
{
    public bool Equals(Car x, Car y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(Car obj)
    {
        return obj.Id.GetHashCode();
    }
}


0 commentaires

0
votes

Vous pouvez utiliser une variable anonyme contenant les deux listes:

var res = new
{
    present = newCars.Where(n => oldCars.Select(o => o.Id).Contains(n.Id)), // present if the old cars list contains the id from the new list
    absent = newCars.Where(n => !oldCars.Select(o => o.Id).Contains(n.Id)) // absent if the old cars list does not contain the id from the new list
};


1 commentaires

Que faire si je dois vérifier non seulement l'identifiant de la voiture, mais également le numéro d'enregistrement. Existe-t-il un moyen d'avoir 2 conditions dans votre Select?



0
votes

Lorsque vous dites que les nouvelles voitures sont la source de la vérité, cela signifie que vous les voulez toujours dans la liste, mais que vous n'avez peut-être pas une vieille voiture équivalente. Vous décrivez une jointure à gauche.

Cela devrait vous donner la plupart de ce que vous voulez.

var finalResult = (Present: results[0], Absent: results[1]);

Le résultat est un tuple contenant la nouvelle voiture et un Présentez qui est true ou false selon qu'il y a une vieille voiture ou non.

Cela vous donne un seul ensemble de résultats . Vous vouliez deux ensembles de résultats sous forme de tuple. Pour obtenir cela, étendez simplement la requête ci-dessus, en regroupant par Présent . Cela vous donne un ensemble de résultats avec exactement deux éléments

var results = (from r in (from n in newCars
        join o in oldCars
        on n.Id equals o.Id
        into cars
        from oc in cars.DefaultIfEmpty()
        select (NewCar: n, Present: oc != null))
    group r by r.Present into g
    orderby g.Key descending 
    select g.Select(x => x.NewCar).ToList()).ToList();

à partir desquels vous pouvez obtenir un seul tuple contenant les deux listes

var results = from n in newCars
    join o in oldCars
    on n.Id == o.Id
    group into cars 
    from oc in cars.DefaultIfEmpty()
    select (NewCar: n, Present: oc != null)

p>


5 commentaires

Je suggérerais de mettre un ToList sur le select final afin que g.Select soit traité une fois pendant la requête. De plus, je pense que votre première requête manque quelque chose: from oc.DefaultIfEmpty () n'a-t-il pas besoin d'une variable de plage? Et vous utilisez n et newcar comme la même variable de plage.


Qu'en est-il du de oc.DefaultIfEmpty () ? Il n'a pas besoin d'une variable de plage?


Je pense que vous étiez en train de regarder une édition précédente (c'était était faux à un moment donné). cars.DefaultIfEmpty () est ce qui est nécessaire pour obtenir des résultats corrects.


Non, première requête voir la phrase?


Oops. Je comprends enfin ce que vous essayez de dire. Je l'ai changé dans un bloc de code mais pas dans l'autre; c'est ce que j'obtiens pour ne pas utiliser de compilateur. Merci d'être diligent!



0
votes

En utilisant une méthode d'extension pour traiter l'ajout à une liste (un tout petit peu) de manière plus fonctionnelle, vous pouvez utiliser LINQ Aggregate pour le faire en un seul passage:

    var idsFromOldCars = oldCars.Select(c => c.Id).ToHashSet();
    var present = new List<Car>();
    var absent = new List<Car>();
    foreach (var nc in newCars) {
        if (idsFromOldCars.Contains(nc.Id))
            present.Add(nc);
        else
            absent.Add(nc);
    }

AfterAdd est défini dans une classe statique comme:

public static class ListExt {
    public static List<T> AfterAdd<T>(this List<T> head, params T[] tail) {
        head.AddRange(tail);
        return head;
    }
}

Mais il est beaucoup plus clair d'utiliser simplement un foreach boucle:

var idsFromOldCars = oldCars.Select(c => c.Id).ToHashSet();
var(present, absent) = newCars.Aggregate((p:new List<Car>(),a:new List<Car>()), (pa,nc) => idsFromOldCars.Contains(nc.Id) ? (pa.p.AfterAdd(nc),pa.a) : (pa.p,pa.a.AfterAdd(nc)));

Dans les deux cas, la création du HashSet garantit que vous n'avez pas à parcourir la liste oldCars pour trouver chaque newCar . Si vous saviez que les listes étaient classées par Id , vous pourriez faire une solution en un seul passage plus compliquée en parcourant les listes en parallèle, mais cela ne semble même pas valoir la complexité.

REMARQUE: Une Rejoindre fait effectivement quelque chose de similaire, en convertissant la deuxième Liste en une Recherche (comme un Dictionnaire ) qui peut être utilisé pour trouver les correspondances avec la première List.


2 commentaires

Puis-je utiliser N'IMPORTE QUELLE à la place? Je peux avoir plus d'une condition à faire correspondre afin de les catégoriser à présenter ou absente? Aider.


@Illep Peut-être pouvez-vous mettre à jour votre question pour être plus claire? Vous semblez avoir des idées contradictoires sur ce que vous voulez ...