C# 3.0: нужно возвращать дубликаты из списка <>

У меня есть список<> объектов в C#, и мне нужен способ вернуть те объекты, которые считаются дубликатами в списке. Мне не нужен отдельный набор результатов, мне нужен список тех элементов, которые я буду удалять из своего репозитория.

Для примера расскажем, у меня есть список типов автомобилей, и мне нужно знать, какие из этих автомобилей того же цвета, что и другие в списке. Вот автомобили в списке и их свойства цвета:

Car1.Color = Red;

Car2.Color = Blue;

Car3.Color = Green;

Car4.Color = Red;

Car5.Color = Red;

Для этого примера мне нужно, чтобы результат (IEnumerable<>, List<>или любой другой) содержал Car4 и Car5, потому что я хочу удалить их из своего хранилища или базы данных, чтобы в моем хранилище была только одна машина на цвет. Любая помощь будет оценена.

8 ответов

Решение

Я непреднамеренно закодировал это вчера, когда пытался написать "отличную от проекции". Я включил! когда я не должен был, но на этот раз это просто правильно:

public static IEnumerable<TSource> DuplicatesBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        // Yield it if the key hasn't actually been added - i.e. it
        // was already in the set
        if (!seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

Затем вы бы назвали это с:

var duplicates = cars.DuplicatesBy(car => car.Color);
var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.Skip(1)
                 select car;

Это группирует автомобили по цвету, а затем пропускает первый результат из каждой группы, возвращая остаток от каждой группы, сведенный в одну последовательность.

Если у вас есть конкретные требования о том, какой из них вы хотите сохранить, например, если автомобиль имеет Id собственности, и вы хотите сохранить машину с самым низким Id, тогда вы можете добавить туда порядок, например

var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.OrderBy(c => c.Id).Skip(1)
                 select car;

Вот немного другое решение Linq, которое, я думаю, делает более очевидным то, что вы пытаетесь сделать:

var s = from car in cars
    group car by car.Color into g
    where g.Count() == 1
    select g.First();

Это просто группировка автомобилей по цвету, отбрасывание всех групп, имеющих более одного элемента, а затем помещение остальных в возвращаемый IEnumerable.

IEnumerable<Car> GetDuplicateColors(List<Car> cars)
{
    return cars.Where(c => cars.Any(c2 => c2.Color == c.Color && cars.IndexOf(c2) < cars.IndexOf(c) ) );
}    

Это в основном означает "вернуть автомобили, если в списке есть машина с таким же цветом и меньшим индексом".

Не уверен в производительности, хотя. Я подозреваю, что подход с O(1) для поиска дубликатов (например, метод словаря /hashset) может быть быстрее для больших наборов.

Создать новый Dictionary<Color, Car> foundColors и List<Car> carsToDelete

Затем вы перебираете свой первоначальный список автомобилей следующим образом:

foreach(Car c in listOfCars)
{
    if (foundColors.containsKey(c.Color))
    {
        carsToDelete.Add(c);
    }
    else
    {
        foundColors.Add(c.Color, c);
    }
}

Затем вы можете удалить каждую машину, которая находится в foundColors.

Вы можете получить небольшое повышение производительности, поместив логику "удаления записи" в if вместо создания нового списка, но то, как вы сформулировали вопрос, предполагает, что вам нужно собрать их в список.

Без фактического кодирования, как насчет алгоритма примерно так:

  • перебрать ваш List<T> создавая Dictionary<T, int>
  • перебрать ваш Dictionary<T, int> удаление записей, где int >1

Что-нибудь осталось в Dictionary есть дубликаты. Вторая часть, где вы действительно удаляете, не является обязательной, конечно. Вы можете просто перебрать Dictionary и ищите>1, чтобы принять меры.

РЕДАКТИРОВАТЬ: ОК, я столкнулся с Райаном, так как он на самом деле дал вам код.;)

public static IQueryable Duplicates(этот источник IEnumerable), где TSource: IComparable {

if (source == null)   
     throw new ArgumentNullException("source");   
 return source.Where(x => source.Count(y=>y.Equals(x)) > 1).AsQueryable<TSource>();   

}

Мой ответ черпает вдохновение (в этом порядке) от респондентов: Джо Коухорна, Грега Бича и Джона Скита.

Я решил привести полный пример, предполагая (для реальной эффективности слов), что у вас есть статический список цветов автомобилей. Я полагаю, что следующий код иллюстрирует полное решение проблемы элегантным, хотя и не обязательно гиперэффективным образом.

#region SearchForNonDistinctMembersInAGenericListSample
public static string[] carColors = new[]{"Red", "Blue", "Green"}; 
public static string[] carStyles = new[]{"Compact", "Sedan", "SUV", "Mini-Van", "Jeep"}; 
public class Car
{
    public Car(){}
    public string Color { get; set; }
    public string Style { get; set; }
}
public static List<Car> SearchForNonDistinctMembersInAList()
{
    // pass in cars normally, but declare here for brevity
    var cars = new List<Car>(5) { new Car(){Color=carColors[0], Style=carStyles[0]}, 
                                      new Car(){Color=carColors[1],Style=carStyles[1]},
                                      new Car(){Color=carColors[0],Style=carStyles[2]}, 
                                      new Car(){Color=carColors[2],Style=carStyles[3]}, 
                                      new Car(){Color=carColors[0],Style=carStyles[4]}};
    List<Car> carDupes = new List<Car>();

    for (int i = 0; i < carColors.Length; i++)
    {
        Func<Car,bool> dupeMatcher = c => c.Color == carColors[i];

        int count = cars.Count<Car>(dupeMatcher);

        if (count > 1) // we have duplicates
        {
            foreach (Car dupe in cars.Where<Car>(dupeMatcher).Skip<Car>(1))
            {
                carDupes.Add(dupe);
            }
        }
    }
    return carDupes;
}
#endregion

Я собираюсь вернуться сюда позже и сравнить это решение со всеми тремя его вдохновителями, просто чтобы противопоставить стили. Это довольно интересно.

Другие вопросы по тегам