Verwenden Sie LINQ oder Lambda anstelle von verschachtelten und mehreren foreach-Anweisungen

141516
Niike2

Ich möchte eine bessere Syntax als geschachtelte foreachAnweisungen verwenden, um die Ausgangsliste mit Elementen aus der zweiten Liste zu überschreiben.

Im Code unten:

  • Ich möchte initialListmit denen überschreiben secondList, die dasselbe haben Value(Remove Red).
  • Verwenden Sie die Elemente, in secondListdenen Valuedas gleiche war (Gelb).

Eine neue initialListListe sollte enthalten (Grün und Gelb).

static void Main(string[] args)
{
    int useProd = 2;
    int useDomain = 0;

    var person1 = new Person() { prodId = 1, Value = "foo", domainId = 0, Name = "Red" };
    var person2 = new Person() { prodId = 1, Value = "bar", domainId = 0, Name = "Green" };
    var person3 = new Person() { prodId = 1, Value = "foo", domainId = 1, Name = "Yellow" };

    var initialList = new List<Person>();
    initialList.Add(person1);
    initialList.Add(person2);

    var secondList = new List<Person>();
    secondList.Add(person3);

    List<Person> personsToRemove = new List<Person>();
    List<Person> personsToUpdate = new List<Person>();

    foreach (var pers1 in initialList)
    {
        foreach (var pers2 in secondList)
        {
            if (pers1.Value == pers2.Value)
            {
                personsToRemove.Add(pers1);
                personsToUpdate.Add(pers2);
            }
        }
    }
    foreach (var remPers in personsToRemove)
    {
        initialList.Remove(remPers);
    }
    foreach (var updPers in personsToUpdate)
    {
        initialList.Add(updPers);
    }
    foreach (var item in initialList)
    {
        Console.WriteLine(String.Format("Value: {0}, prodId: {1}, domainId: {2}, Name: {3}", item.Value, item.prodId, item.domainId, item.Name));
    }

    Console.ReadKey();
}
public class Person
{
    public int prodId { get; set; }
    public string Value { get; set; }
    public int domainId { get; set; }
    public string Name { get; set; }
}
Antworten
11
Willkommen bei Code Review. Ich hoffe, du bekommst gute Antworten. Heslacher vor 5 Jahren 1
Mir ist unklar, was genau gemacht wird. Können Sie die Geschäftsidee hinter dem Code erläutern? Jeroen Vannevel vor 5 Jahren 0
@JeroenVannevel Der Code ist gegenüber dem Geschäftscode zu stark vereinfacht. Ich muss zwei Listen zusammenführen. Ich kann nicht nur beides zusammen hinzufügen, ich muss die initialList-Objekte mit den Objekten von secondList überschreiben, wobei die Eigenschaft Value dieselbe ist. Und ich brauche auch alle Objekte von secondList, die nicht in initialList vorhanden sind. Wenn foreach-Anweisungen initial.Value == second.Value finden, entfernen Sie stattdessen (anfängliches Objekt) und fügen Sie sie hinzu (zweites Objekt). Gibt es eine andere Möglichkeit, dies in Linq oder Lambda zu tun? Niike2 vor 5 Jahren 0
Wenn Sie sagen "* Ich brauche auch alle Objekte von secondList, die nicht in initialList * vorhanden sind", wie stellen Sie fest, dass etwas in `initialList` nicht existiert? Was bestimmt die Einzigartigkeit? Jeroen Vannevel vor 5 Jahren 0
Die Einzigartigkeit kommt von anderen Eigenschaften wie der Id-Spalte in der DB. Wie ich bereits sagte, ist dies ein stark vereinfachtes Beispiel. Weitere Eigenschaften des Objekts. Wenn ich im Geschäftscode initialList und secondList erstelle, werden sie aus der Datenbank ausgefüllt, aber für secondList wird eine Id-Spalte unterschieden. Niike2 vor 5 Jahren 0

4 Antworten auf die Frage

11
Dan Lyons

Da Sie in den Kommentaren erwähnt haben, dass Sie die Vereinigung der beiden Listen wünschen, können Sie eine LINQ durchführen Union. Es gibt einige Möglichkeiten, dies zu tun:

Benutzen IEnumerable<T>.Union(IEnumerable<T>)

Wenn Sie mit der einfacheren Überlast gehen Union, müssen Sie implementieren IEquatable<T>und außer Kraft setzt GetHashCodeauf Person.
Zur Vereinfachung betrachtet mein Beispiel nur die Value-Eigenschaft:

public class Person : IEquatable<Person>
{
    public int prodId { get; set; }
    public string Value { get; set; }
    public int domainId { get; set; }
    public string Name { get; set; }
    public override bool Equals(object obj)
    {
       return Equals(obj as Person);
    }

    public override int GetHashCode()
    {
       return Value == null ? 0 : Value.GetHashCode();
    }

    public bool Equals(Person other)
    {
       return other != null
              && other.Value == Value;
    }
}

Verwendungsbeispiel:

static void Main(string[] args)
{
    var list1 = new List<Person>
                {
                   new Person {Value = "bob"},
                   new Person {Value = "alice"},
                };

    var list2 = new List<Person>
                {
                   new Person {Value = "bob"},
                   new Person {Value = "charles"},
                };

    var unionedList = list1.Union(list2).ToList();
}

Benutzen IEnumerable<T>.Union(IEnumerable<T>, IEqualityComparer<T>)

Wenn Sie sich für diese Option entscheiden, können Sie den Gleichheitsvergleicher von @ Heslacher als Parameter für den UnionAufruf verwenden.

static void Main(string[] args)
{
    var list1 = new List<Person>
                {
                   new Person {Value = "bob"},
                   new Person {Value = "alice"},
                };

    var list2 = new List<Person>
                {
                   new Person {Value = "bob"},
                   new Person {Value = "charles"},
                };

    var unionedList = list1.Union(list2, new PersonValueComaparer()).ToList();
}

Bestellangelegenheiten

Da Sie secondListin Konflikten gewinnen möchten, würden Sie verwenden secondList.Union(initialList), sodass alle Übereinstimmungen auf die Objekte in verschoben werden secondList.

@Heslacher Ich habe das am Ende erwähnt, aber bei der Formatierung kann ich sehen, dass es leicht übersehen werden könnte. Ich werde die Antwort ein wenig formatieren. Dan Lyons vor 5 Jahren 0
10
Heslacher

By using an IEqualityComparer<Person> which only compares the Value property like

public class PersonValueComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (x == null && y == null) { return true; }
        if (x == null || y == null) { return false; }

        return x.Value == y.Value;
    }

    public int GetHashCode(Person obj)
    {
        if (obj == null || obj.Value == null) {return 0;}
        return obj.Value.GetHashCode();
    }
}  

you can by using linq methods replace the loops by

        PersonValueComparer valueComparer = new PersonValueComparer();
        initialList = secondList.Where(p => initialList.Contains(p, valueComparer))
            .Concat(initialList.Where(p => !secondList.Contains(p, valueComparer))).ToList();

We are first using the Where() method to filter the secondList for items which are in the initialList . Then we use Concat() to add the Person''s which are not in thesecondList`.


Based on the naming guidelines properties should be named using PascalCase casing.


You should always use meaningful and descriptive names for naming methods, variables, properties and classes so Sam the Maintainer sees at first glance what it is about.


Shortening variable names doesn't add any value but instead reduces readability which Sam the Maintainer doesn't like.


Ist die .Select in diesem Beispiel nicht völlig wertlos? Bryan Boettcher vor 5 Jahren 2
@insta hast du recht. Antwort aktualisiert Heslacher vor 5 Jahren 0
7
Alexey Adamsky

Wie wäre es damit:

var peopleToAdd = secondList.Where(p1 => initialList.Any(p2 => p1.Value == p2.Value)).ToList();
initialList.RemoveAll(p1 => secondList.Any(p2 => p1.Value == p2.Value));
initialList.AddRange(peopleToAdd);

Schritt 1: Schauen Sie sich die zweite Liste an und finden Sie alle Personen, die der ersten Liste hinzugefügt werden müssen. Wir suchen alle Personen, die Valuein beiden Listen dasselbe haben . Hinweis: Führen .ToList()Sie den Befehl aus, um das Ergebnis zu erhalten, bevor der Schritt 2 ausgeführt wird.

var peopleToAdd = secondList.Where(p1 => initialList.Any(p2 => p1.Value == p2.Value)).ToList();

Schritt 2 - Entfernen Sie alle Personen, die in der zweiten Liste vorkommen, mit der gleichen Liste aus der ersten Liste Value.

initialList.RemoveAll(p1 => secondList.Any(p2 => p1.Value == p2.Value));

Hinweis: Micro-Optimierung für den Schritt 2, können Sie peopleToAddstatt, secondListda wir schon passende Menschen entdeckt.

initialList.RemoveAll(p1 => peopleToAdd.Any(p2 => p1.Value == p2.Value));

Schritt 3 - fügen Sie alle in Schritt 1 gefundenen Personen zum hinzu initialList.

initialList.AddRange(peopleToAdd);
Ist meine Erklärung jetzt sinnvoll? Alexey Adamsky vor 5 Jahren 1
Viel besser. Danke dir. Wir möchten hier mehr über Code Review erfahren. Vielen Dank, dass Sie sich die Zeit genommen haben, um zu erklären, was los ist. Malachi vor 5 Jahren 1
5
almaz

Was Sie tatsächlich tun, ist die Aktualisierung initialListvon Objekten secondListanhand von Übereinstimmungen Value. Die optimale Lösung hätte O (N + M) -Vergleiche (N und M - Anzahl der Elemente in beiden Listen), während Ihre Lösung O (N * M) -Vergleiche durchführt.

Um Ihren Code zu vereinfachen, schlage ich vor, die Nachschlagetabelle folgendermaßen zu verwenden:

var lookup = secondList.ToLookup(person => person.Value);

initialList = initialList
    .Select(person => lookup[person.Value].FirstOrDefault() ?? person).ToList();

Wir "gehen" einmal durch die secondListSammlung, um eine Nachschlagetabelle zu erstellen, und gehen einmal durch die initialListListe, um Elemente zu ersetzen, die dem Element in entsprechen lookup.