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
Я собираюсь вернуться сюда позже и сравнить это решение со всеми тремя его вдохновителями, просто чтобы противопоставить стили. Это довольно интересно.