C # 3.0:Нужно вернуть дубликаты из списка<>
-
20-08-2019 - |
Вопрос
У меня есть Список<> объектов в C #, и мне нужен способ вернуть те объекты, которые считаются дубликатами в списке.Мне не нужен отдельный результирующий набор, мне нужен список тех элементов, которые я буду удалять из своего репозитория.
Ради этого примера допустим, у меня есть список типов "Автомобилей", и мне нужно знать, какой из этих автомобилей того же цвета, что и другой в списке.Вот автомобили в списке и их свойство цвета:
Car1.Color = Red;
Car2.Color = Blue;
Car3.Color = Green;
Car4.Color = Red;
Car5.Color = Red;
Для этого примера мне нужен результат (IEnumerable<>, Список<>, или что-то еще), чтобы содержать Car4 и Car5, потому что я хочу удалить их из своего репозитория или базы данных, чтобы в моем репозитории было только по одному автомобилю на цвет.Будем признательны за любую помощь.
Решение
Я непреднамеренно закодировал это вчера, когда пытался написать "distinct by a projection".Я включил !когда я не должен был этого делать, но на этот раз все в самый раз:
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) (например, метод dictionary / hashset) может быть быстрее для больших наборов.
Создайте новый Dictionary<Color, Car> foundColors
и a 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, чтобы предпринять действия.
Редактировать:Хорошо, я позвонил Райану, так как он действительно дал тебе код.;)
Мой ответ черпает вдохновение (в таком порядке) у респондентов-подписчиков:Джо Кохорн, Грег Бич и Джон Скит.
Я решил привести полный пример, исходя из предположения (для реальной эффективности использования word), что у вас есть статический список цветов автомобиля.Я полагаю, что следующий код иллюстрирует полное решение проблемы элегантным, хотя и не обязательно сверхэффективным способом.
#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
Я собираюсь вернуться к этому позже и сравнить это решение со всеми тремя его вдохновителями, просто чтобы сравнить стили.Это довольно интересно.
общедоступные статические дубликаты, доступные IQueryable (этот источник IEnumerable), где TSource :Несравнимый {
if (source == null)
throw new ArgumentNullException("source");
return source.Where(x => source.Count(y=>y.Equals(x)) > 1).AsQueryable<TSource>();
}