Объединить два списка, сравнивая свойства их элементов

#c# #linq

#c# #linq

Вопрос:

 public class Person()
    {
    int ID;
    string Name;
    DateTime ChangeDate
    } 



var list1 = new List<Person>
    {
        new Person { ID= 1, Name = "Peter", ChangeDate= "2011-10-21" },
        new Person { ID= 2, Name = "John",  ChangeDate= "2011-10-22" },
        new Person { ID= 3, Name = "Mike",  ChangeDate= "2011-10-23" },
        new Person { ID= 4, Name = "Dave",  ChangeDate= "2011-10-24" }
    };

 var list2 = new List<Person>
    {
        new Person { ID= 1, Name = "Pete",  ChangeDate= "2011-10-21" },
        new Person { ID= 2, Name = "Johny", ChangeDate= "2011-10-20" },
        new Person { ID= 3, Name = "Mikey", ChangeDate= "2011-10-24" },
        new Person { ID= 5, Name = "Larry", ChangeDate= "2011-10-27" }
    };
  

В качестве выходных данных я хотел бы иметь list1 list2 =

     Person { ID= 1, Name = "Peter", ChangeDate= "2011-10-21" },
    Person { ID= 2, Name = "John",  ChangeDate= "2011-10-22" },
    Person { ID= 3, Name = "Mikey", ChangeDate= "2011-10-24" },
    Person { ID= 4, Name = "Dave",  ChangeDate= "2011-10-24" }
    Person { ID= 5, Name = "Larry", ChangeDate= "2011-10-27" }
  

И алгоритм такой.
Объединить два списка. Если элементы списков имеют одинаковый идентификатор, сравните их по changeDate и выберите ond с большей датой. Если измененные значения равны, возьмите любой из них, но не оба.
Возможно, проще объединить оба списка, чем фильтровать их с помощью lambda. Я пытался, но всегда получался какой-то уродливый код :/

У кого-нибудь есть идеи?

Комментарии:

1. Если можно, я бы использовал DateTime вместо строковых значений.

Ответ №1:

LINQ

 var q = from person in list1.Concat(list2)
        group person by person.ID into g
        select g.OrderByDescending(p => p.ChangeDate).First();
  

Ответ №2:

Объединить оба списка, сортируя по убыванию даты. Теперь вам нужно выбрать первое вхождение каждого идентификатора в вашем отсортированном списке.

Ответ №3:

Как насчет чего-то вроде этого?

 using System;
using System.Collections.Generic;
using System.Linq;

public class Person
{
    public int ID;
    public string Name;
    public DateTime ChangeDate;
}

public class PersonComparer : IEqualityComparer<Person>
{
    public bool Equals(Person p1, Person p2)
    {
        return p1.ID == p2.ID;
    }

    public int GetHashCode(Person p)
    {
        return p.ID.GetHashCode();
    }
}

class Program
{
    static void Main(string[] args)
    {
        var list1 = new List<Person>
        {
            new Person { ID = 1, Name = "Peter", ChangeDate = DateTime.Parse("2011-10-21") },
            new Person { ID = 2, Name = "John",  ChangeDate = DateTime.Parse("2011-10-22") },
            new Person { ID = 3, Name = "Mike",  ChangeDate = DateTime.Parse("2011-10-23") },
            new Person { ID = 4, Name = "Dave",  ChangeDate = DateTime.Parse("2011-10-24") }
        };

        var list2 = new List<Person>
        {
            new Person { ID = 1, Name = "Pete",  ChangeDate = DateTime.Parse("2011-10-21") },
            new Person { ID = 2, Name = "Johny", ChangeDate = DateTime.Parse("2011-10-20") },
            new Person { ID = 3, Name = "Mikey", ChangeDate = DateTime.Parse("2011-10-24") },
            new Person { ID = 5, Name = "Larry", ChangeDate = DateTime.Parse("2011-10-27") }
        };

        var pc = new PersonComparer();
        var combined = list1.Join(list2, p => p.ID, p => p.ID, (p1,p2) => p2.ChangeDate > p1.ChangeDate ? p2 : p1)
                            .Union(list1.Except(list2, pc))
                            .Union(list2.Except(list1, pc));

        foreach(var p in combined)
        {
            Console.WriteLine(p.ID   " "   p.Name   " "   p.ChangeDate);
        }
    }
}
  

Ответ №4:

Вы могли бы объединить и отсортировать их, а затем получить разные значения:

 class PersonIdEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        return x.ID == y.ID;
    }

    public int GetHashCode(Person person)
    {
        return person.ID;
    }
}

var result = list1.Concat(list2)
    .OrderByDescending(i => DateTime.Parse(i.ChangeDate)) // Most recent first
    .Distinct(new PersonIdEqualityComparer())
    ;
  

Это предполагает, что Distinct из каждого набора будет извлечен первый встреченный элемент, а не произвольный элемент. Учитывая, что это, вероятно, просто вставляет их в HashSet во время обхода коллекции, мне это кажется разумным.

Он также не выполняет проверку ошибок. Если какие-либо значения могут быть нулевыми или ChangeDate могут быть недопустимыми, то этот код будет генерировать исключения. Если это может быть проблемой, я предлагаю вам проверить свои данные перед их передачей и выполнить проверку ошибок в PersonIdEqualityComparer классе.