С# 3.0: нужно возвращать дубликаты из списка <>
У меня есть List < > объектов в С#, и мне нужен способ вернуть те объекты, которые считаются дублирующими в списке. Мне не нужен набор результатов Distinct, мне нужен список тех элементов, которые я удалю из своего репозитория.
Для этого примера, скажем, у меня есть список типов "Автомобиль", и мне нужно знать, какие из этих автомобилей имеют тот же цвет, что и другой в списке. Вот автомобили в списке и их свойство цвета:
Car1.Color = Red;
Car2.Color = Blue;
Car3.Color = Green;
Car4.Color = Red;
Car5.Color = Red;
Для этого примера мне нужен результат (IEnumerable < > , List < > или что-то еще), чтобы содержать Car4 и Car5, потому что я хочу удалить их из своего репозитория или db, чтобы у меня был только один автомобиль за цвет в моем репозитории, Любая помощь будет оценена.
Ответы
Ответ 1
Я нечаянно закодировал это вчера, когда я пытался написать "отличную от проекции". Я включил! когда я не должен был, но на этот раз это правильно:
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);
Ответ 2
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;
Ответ 3
Здесь немного другое решение Linq, которое, я думаю, делает более очевидным то, что вы пытаетесь сделать:
var s = from car in cars
group car by car.Color into g
where g.Count() == 1
select g.First();
Он просто группирует автомобили по цвету, выкидывая все группы с более чем одним элементом, а затем помещая остальные в возвращаемый IEnumerable.
Ответ 4
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) поиском дубликатов (например, метод dictionary/hashset) может быть быстрее для больших наборов.
Ответ 5
Создайте новый 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
вместо создания нового списка, но так, как вы сформулировали вопрос, предложили, чтобы вам нужно было собрать их в списке.
Ответ 6
Без его кодирования, как насчет алгоритма примерно так:
- итерации через
List<T>
создание Dictionary<T, int>
- итерации через
Dictionary<T, int>
удаление записей, в которых int
есть > 1
Все, что осталось в Dictionary
, имеет дубликаты. Конечно, вторая часть, где вы фактически удаляете, является необязательной. Вы можете просто прокручивать через Dictionary
и искать > 1, чтобы принять меры.
EDIT: Хорошо, я натолкнулся на Райана, так как он действительно дал вам код.;)
Ответ 7
Мой ответ берет вдохновение (в этом порядке) от респондентов-последователей: Джо Кохорн, Грег Бич и Джон Скит.
Я решил представить полный пример, исходя из предположения (для реального эффективного использования слова), что у вас есть статический список цветов автомобилей. Я считаю, что следующий код иллюстрирует полное решение проблемы в элегантной, хотя и не обязательно гиперэффективной манере.
#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
Я собираюсь вернуться сюда позже и сравнить это решение со всеми тремя его вдохновениями, просто чтобы сравнить стили. Это довольно интересно.
Ответ 8
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>();
}