C # 3.0: нужно возвращать дубликаты из списка <> - PullRequest
19 голосов
/ 30 января 2009

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

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

Car1.Color = Red;

Car2.Color = Blue;

Car3.Color = Green;

Car4.Color = Red;

Car5.Color = Red;

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

Ответы [ 8 ]

29 голосов
/ 30 января 2009

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

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);
17 голосов
/ 30 января 2009
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;
5 голосов
/ 30 января 2009

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

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

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

3 голосов
/ 30 января 2009

Создайте новый 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 вместо создания нового списка, но способ, которым вы сформулировали вопрос, предполагает, что вам нужно собрать их в Список.

3 голосов
/ 30 января 2009
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) для поиска дубликатов (как метод словаря / хэш-набора) может быть быстрее для больших наборов.

0 голосов
/ 30 января 2009

публичные статические 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>();   

}

0 голосов
/ 30 января 2009

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

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

#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

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

0 голосов
/ 30 января 2009

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

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

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

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

...