У кого-нибудь есть быстрый способ дедупликации общего списка на C#?
Однострочное решение: ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);





Отсортируйте его, затем отметьте два и два рядом друг с другом, так как дубликаты будут слипаться.
Что-то вроде этого:
list.Sort();
Int32 index = list.Count - 1;
while (index > 0)
{
if (list[index] == list[index - 1])
{
if (index < list.Count - 1)
(list[index], list[list.Count - 1]) = (list[list.Count - 1], list[index]);
list.RemoveAt(list.Count - 1);
index--;
}
else
index--;
}
Примечания:
Если я не ошибаюсь, большинство из упомянутых выше подходов - это просто абстракции именно этих подпрограмм, не так ли? Я бы воспользовался вашим подходом, Лассе, потому что это то, как я мысленно представляю движение через данные. Но теперь меня интересуют различия в производительности между некоторыми предложениями.
Реализуйте их и рассчитайте время, только так можно быть уверенным. Даже нотация Big-O не поможет вам с фактическими показателями производительности, а только взаимосвязь эффекта роста.
Мне нравится этот подход, он более переносим на другие языки.
Не делай этого. Это очень медленно. RemoveAt - очень дорогостоящая операция на List.
Клеман прав. Чтобы спасти это, можно было бы обернуть это в метод, который дает с перечислителем и возвращает только отдельные значения. В качестве альтернативы вы можете скопировать значения в новый массив или список.
это кажется связанным, обсуждает проблему медленного удаления в списке stackoverflow.com/questions/6926554/…
Вместо использования RemoveAt вы должны заменить последнее значение в списке. Затем удалите последние предметы (уменьшив счет). Затем снова выполните сортировку.
@darkgaze Я взял на себя смелость улучшить ваше предложение, запустив цикл сравнения от начала до конца, я мог избежать использования курорта в цикле.
Однако в C# есть гораздо лучшие альтернативы, такие как отчет Distinct выше, поэтому я не думаю, что кто-то все равно будет использовать этот код.
@ LasseVågsætherKarlsen Спасибо. Где вы его модифицировали? Я тоже написал ответ в этой ветке. stackoverflow.com/a/56344965/772739
Возможно, вам стоит подумать об использовании HashSet.
Из ссылки MSDN:
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
HashSet<int> evenNumbers = new HashSet<int>();
HashSet<int> oddNumbers = new HashSet<int>();
for (int i = 0; i < 5; i++)
{
// Populate numbers with just even numbers.
evenNumbers.Add(i * 2);
// Populate oddNumbers with just odd numbers.
oddNumbers.Add((i * 2) + 1);
}
Console.Write("evenNumbers contains {0} elements: ", evenNumbers.Count);
DisplaySet(evenNumbers);
Console.Write("oddNumbers contains {0} elements: ", oddNumbers.Count);
DisplaySet(oddNumbers);
// Create a new HashSet populated with even numbers.
HashSet<int> numbers = new HashSet<int>(evenNumbers);
Console.WriteLine("numbers UnionWith oddNumbers...");
numbers.UnionWith(oddNumbers);
Console.Write("numbers contains {0} elements: ", numbers.Count);
DisplaySet(numbers);
}
private static void DisplaySet(HashSet<int> set)
{
Console.Write("{");
foreach (int i in set)
{
Console.Write(" {0}", i);
}
Console.WriteLine(" }");
}
}
/* This example produces output similar to the following:
* evenNumbers contains 5 elements: { 0 2 4 6 8 }
* oddNumbers contains 5 elements: { 1 3 5 7 9 }
* numbers UnionWith oddNumbers...
* numbers contains 10 elements: { 0 2 4 6 8 1 3 5 7 9 }
*/
это невероятно быстро ... 100000 строк со списком занимают 400 с и 8 МБ оперативной памяти, мое собственное решение занимает 2,5 с и 28 МБ, hashset занимает 0,1 с !!! и 11 МБ оперативной памяти
HashSetне имеет индекса, поэтому использовать его не всегда возможно. Мне нужно один раз создать огромный список без дубликатов, а затем использовать его для ListView в виртуальном режиме. Было очень быстро создать HashSet<>, а затем преобразовать его в List<> (чтобы ListView мог получить доступ к элементам по индексу). List<>.Contains() работает слишком медленно.
Помогло бы, если бы был пример того, как использовать хэш-набор в этом конкретном контексте.
Как это можно считать ответом? Это ссылка
HashSet отлично подходит в большинстве случаев. Но если у вас есть такой объект, как DateTime, он сравнивается по ссылке, а не по значению, поэтому вы все равно получите дубликаты.
Если кто-то еще захочет использовать HashSet <T> внутри шаблона T4, вам необходимо добавить явную ссылку на сборку System.Core: <# @ assembly name = "System.Core" #> <# @ import namespace = "System.Collections.Generic" #> (см. stackoverflow.com/questions/247005/…)
@sasjaq, почему бы вам не опубликовать свое решение, если оно такое хорошее и быстрое?
@barlop мое решение в 150 раз быстрее, чем сам список, но в 25 раз медленнее, чем hashset ... кстати, мое решение состоит в том, чтобы разделить каждую строку на двухбуквенные ключи и создать древовидную структуру, где вы можете сравнить путь для каждой двухбуквенной полосы искомого термина .
обратите внимание, что в linq Distinct используется Set<T>linksource.microsoft.com/#System.Core/System/Linq/…
@JasonMcKindly: почему ты так думаешь? Ему просто нужно предоставить значимый Equals + GetHashCode (верно для DateTime). Даже если в типе его нет, вы можете использовать HashSet-конструктор, который принимает IEqualityComparer<T> в качестве аргумента.
HashSet не подходит, когда у вас очень мало элементов (300 вместо 10.000). Это НАМНОГО быстрее, используя список или, что еще лучше, массив, добавляйте нужные элементы, затем сортируйте их непосредственно перед тем, как они вам понадобятся, а затем удаляйте дубликаты. Даже делать все это вручную быстрее, чем использовать HashSet для очень небольшого количества элементов.
В Java (я предполагаю, что C# более или менее идентичен):
list = new ArrayList<T>(new HashSet<T>(list))
Если вы действительно хотели изменить исходный список:
List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);
Чтобы сохранить порядок, просто замените HashSet на LinkedHashSet.
в C# это будет: List <T> noDupes = new List <T> (new HashSet <T> (list)); list.Clear (); list.AddRange (noDupes);
В C# это проще: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes); :)
Если вам не важен порядок, вы можете просто засунуть элементы в HashSet, если вы делать хотите поддерживать порядок, вы можете сделать что-то вроде этого:
var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
if (hs.Add(t))
unique.Add(t);
Или способ Linq:
var hs = new HashSet<T>();
list.All( x => hs.Add(x) );
Редактировать: Метод HashSet - это время O(N) и пространство O(N), в то время как сортировка и последующее создание уникальных (как предлагает @ лассевк и другие) - это время O(N*lgN) и пространство O(1), поэтому мне не так ясно (как это было на первый взгляд), что способ сортировки уступает (мои извинения за временное голосование против ...)
Как насчет:
var noDupes = list.Distinct().ToList();
В .net 3.5?
Дублирует ли список?
@darkgaze просто создает еще один список только с уникальными записями. Таким образом, любые дубликаты будут удалены, и у вас останется список, в котором каждая позиция имеет свой объект.
Работает ли это для списка элементов списка, где коды элементов дублируются, и необходимо получить уникальный список
Если вы используете .Net 3+, вы можете использовать Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Этот код завершится ошибкой, поскольку .Distinct () возвращает IEnumerable <T>. Вы должны добавить к нему .ToList ().
Этот подход можно использовать только для списка с простыми значениями.
Нет, он работает со списками, содержащими объекты любого типа. Но вам придется переопределить компаратор по умолчанию для вашего типа. Примерно так: public override bool Equals (object obj) {...}
Всегда полезно переопределить ToString () и GetHashCode () своими классами, чтобы такие вещи работали.
Вы также можете использовать пакет MoreLinQ Nuget, который имеет метод расширения .DistinctBy (). Довольно полезно.
Не гарантируется, что Distinct сохранит порядок, зависит от реализации
Мой требуемый список <MyType> noDupes = withDupes.Distinct (new MyType ()). ToList (); чтобы работать.
Как сказал kronoz в .Net 3.5, вы можете использовать Distinct().
В .Net 2 вы можете имитировать это:
public IEnumerable<T> DedupCollection<T> (IEnumerable<T> input)
{
var passedValues = new HashSet<T>();
// Relatively simple dupe check alg used as example
foreach(T item in input)
if (passedValues.Add(item)) // True if item is new
yield return item;
}
Это может быть использовано для дедупликации любой коллекции и вернет значения в исходном порядке.
Обычно гораздо быстрее фильтровать коллекцию (как это делают и Distinct(), и этот образец), чем удалять из нее элементы.
Проблема с этим подходом заключается в том, что он O (N ^ 2) -ш, в отличие от хеш-набора. Но по крайней мере очевидно, что он делает.
@DrJokepu - на самом деле я не осознавал, что конструктор HashSet выполняет дедупликацию, что делает его лучше в большинстве случаев. Однако это сохранит порядок сортировки, которого нет в HashSet.
HashSet <T> был представлен в 3.5
@thorn правда? Так сложно уследить. В этом случае вы можете просто использовать Dictionary<T, object>, заменив .Contains на .ContainsKey и .Add(item) на .Add(item, null).
@Keith, согласно моему тестированию, HashSet сохраняет порядок, а Distinct() - нет.
@DennisT HashSet иногда делает, в зависимости от типа используемого ключа и относительного порядка ввода. Фрагмент DedupCollection вернет результаты в том же порядке, в котором они были.
@Keith, хорошо, HashSet, похоже, сохраняет порядок, по крайней мере, для int. Вы знаете, для чего еще он работает, а для чего не работает? Я бы проверил себя, но сейчас нет времени.
Просто инициализируйте HashSet списком того же типа:
var noDupes = new HashSet<T>(withDupes);
Или, если вы хотите вернуть список:
var noDupsList = new HashSet<T>(withDupes).ToList();
... и если вам нужен List<T>, используйте new HashSet<T>(withDupes).ToList()
Метод расширения может быть достойным способом ... примерно так:
public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
return listToDeduplicate.Distinct().ToList();
}
А потом вызовите вот так, например:
List<int> myFilteredList = unfilteredList.Deduplicate();
Другой способ в .Net 2.0
static void Main(string[] args)
{
List<string> alpha = new List<string>();
for(char a = 'a'; a <= 'd'; a++)
{
alpha.Add(a.ToString());
alpha.Add(a.ToString());
}
Console.WriteLine("Data :");
alpha.ForEach(delegate(string t) { Console.WriteLine(t); });
alpha.ForEach(delegate (string v)
{
if (alpha.FindAll(delegate(string t) { return t == v; }).Count > 1)
alpha.Remove(v);
});
Console.WriteLine("Unique Result :");
alpha.ForEach(delegate(string t) { Console.WriteLine(t);});
Console.ReadKey();
}
Вот метод расширения для удаления соседних дубликатов на месте. Сначала вызовите Sort () и передайте тот же IComparer. Это должно быть более эффективным, чем версия Лассе В. Карлсена, которая многократно вызывает RemoveAt (что приводит к перемещению нескольких блоков памяти).
public static void RemoveAdjacentDuplicates<T>(this List<T> List, IComparer<T> Comparer)
{
int NumUnique = 0;
for (int i = 0; i < List.Count; i++)
if ((i == 0) || (Comparer.Compare(List[NumUnique - 1], List[i]) != 0))
List[NumUnique++] = List[i];
List.RemoveRange(NumUnique, List.Count - NumUnique);
}
Есть много способов решения - проблема с дубликатами в Списке, ниже один из них:
List<Container> containerList = LoadContainer();//Assume it has duplicates
List<Container> filteredList = new List<Container>();
foreach (var container in containerList)
{
Container duplicateContainer = containerList.Find(delegate(Container checkContainer)
{ return (checkContainer.UniqueId == container.UniqueId); });
//Assume 'UniqueId' is the property of the Container class on which u r making a search
if (!containerList.Contains(duplicateContainer) //Add object when not found in the new class object
{
filteredList.Add(container);
}
}
Ваше здоровье Рави Ганесан
Вот простое решение, которое не требует какого-либо трудночитаемого LINQ или какой-либо предварительной сортировки списка.
private static void CheckForDuplicateItems(List<string> items)
{
if (items == null ||
items.Count == 0)
return;
for (int outerIndex = 0; outerIndex < items.Count; outerIndex++)
{
for (int innerIndex = 0; innerIndex < items.Count; innerIndex++)
{
if (innerIndex == outerIndex) continue;
if (items[outerIndex].Equals(items[innerIndex]))
{
// Duplicate Found
}
}
}
}
С помощью этого метода у вас будет больше контроля над повторяющимися элементами. Даже больше, если у вас есть база данных, которую нужно обновить. Почему для innerIndex не начинать с outerIndex + 1 вместо того, чтобы каждый раз начинать с начала?
Может быть проще просто убедиться, что дубликаты не добавляются в список.
if (items.IndexOf(new_item) < 0)
items.add(new_item)
Сейчас я делаю это так, но чем больше у вас записей, тем дольше длится проверка на наличие дубликатов.
У меня здесь такая же проблема. Я использую метод List<T>.Contains каждый раз, но с более чем 1 000 000 записей. Этот процесс замедляет работу моего приложения. Вместо этого я сначала использую List<T>.Distinct().ToList<T>().
Этот метод очень медленный
Мне нравится использовать эту команду:
List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
.GroupBy(s => s.City)
.Select(grp => grp.FirstOrDefault())
.OrderBy(s => s.City)
.ToList();
В моем списке есть эти поля: Id, StoreName, City, PostalCode Я хотел показать список городов в раскрывающемся списке с повторяющимися значениями. Решение: сгруппируйте по городам, затем выберите первый в списке.
Я надеюсь, что это помогает :)
Это сработало для случая, когда у меня было несколько элементов с одним и тем же ключом, и мне пришлось оставить только тот, у которого была самая последняя дата обновления. Так что подход, использующий «отдельный», не сработает.
У меня это сработало. просто используйте
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
Замените "Тип" желаемым типом, например внутр.
Distinct находится в Linq, а не в System.Collections.Generic, как сообщается на странице MSDN.
Этот ответ (2012 г.) кажется таким же, как два других ответа на этой странице, относящиеся к 2008 г.?
Ответ Дэвида Дж. - хороший метод, нет необходимости в дополнительных объектах, сортировке и т.д. Однако его можно улучшить:
for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)
Таким образом, внешний цикл идет вверху вниз для всего списка, а внутренний цикл идет вниз, «пока не будет достигнута позиция внешнего цикла».
Внешний цикл обеспечивает обработку всего списка, внутренний цикл находит фактические дубликаты, это может произойти только в той части, которую внешний цикл еще не обработал.
Или, если вы не хотите делать внутренний цикл снизу вверх, вы можете запустить внутренний цикл с outerIndex + 1.
public static void RemoveDuplicates<T>(IList<T> list )
{
if (list == null)
{
return;
}
int i = 1;
while(i<list.Count)
{
int j = 0;
bool remove = false;
while (j < i && !remove)
{
if (list[i].Equals(list[j]))
{
remove = true;
}
j++;
}
if (remove)
{
list.RemoveAt(i);
}
else
{
i++;
}
}
}
В качестве вспомогательного метода (без Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
Я думаю, что Distinct уже занят. Кроме того (если вы переименуете метод), он должен работать.
Установив пакет ПодробнееLINQ через Nuget, вы можете легко разделить список объектов по свойству
IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode);
Вы можете использовать Union
obj2 = obj1.Union(obj1).ToList();
Объяснение Почему, это сработает, определенно улучшит этот ответ
Use Linq's Union method.
Примечание. Это решение не требует знания Linq, кроме того, что он существует.
Код
Начните с добавления следующего в начало файла класса:
using System.Linq;
Теперь вы можете использовать следующее для удаления дубликатов из объекта obj1:
obj1 = obj1.Union(obj1).ToList();
Примечание: переименуйте obj1 в имя вашего объекта.
Как это устроено
Команда Union выводит по одной из каждой записи двух исходных объектов. Поскольку obj1 является обоими исходными объектами, это сокращает obj1 до одной из каждой записи.
ToList() возвращает новый список. Это необходимо, потому что команды Linq, такие как Union, возвращают результат как результат IEnumerable вместо того, чтобы изменять исходный список или возвращать новый список.
Простая интуитивно понятная реализация:
public static List<PointF> RemoveDuplicates(List<PointF> listPoints)
{
List<PointF> result = new List<PointF>();
for (int i = 0; i < listPoints.Count; i++)
{
if (!result.Contains(listPoints[i]))
result.Add(listPoints[i]);
}
return result;
}
Этот метод тоже медленный. Создает новый список.
Если у вас есть классы буксировки Product и Customer, и мы хотим удалить повторяющиеся элементы из их списка
public class Product
{
public int Id { get; set; }
public string ProductName { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string CustomerName { get; set; }
}
Вы должны определить универсальный класс в форме ниже
public class ItemEqualityComparer<T> : IEqualityComparer<T> where T : class
{
private readonly PropertyInfo _propertyInfo;
public ItemEqualityComparer(string keyItem)
{
_propertyInfo = typeof(T).GetProperty(keyItem, BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public);
}
public bool Equals(T x, T y)
{
var xValue = _propertyInfo?.GetValue(x, null);
var yValue = _propertyInfo?.GetValue(y, null);
return xValue != null && yValue != null && xValue.Equals(yValue);
}
public int GetHashCode(T obj)
{
var propertyValue = _propertyInfo.GetValue(obj, null);
return propertyValue == null ? 0 : propertyValue.GetHashCode();
}
}
затем вы можете удалить повторяющиеся элементы в своем списке.
var products = new List<Product>
{
new Product{ProductName = "product 1" ,Id = 1,},
new Product{ProductName = "product 2" ,Id = 2,},
new Product{ProductName = "product 2" ,Id = 4,},
new Product{ProductName = "product 2" ,Id = 4,},
};
var productList = products.Distinct(new ItemEqualityComparer<Product>(nameof(Product.Id))).ToList();
var customers = new List<Customer>
{
new Customer{CustomerName = "Customer 1" ,Id = 5,},
new Customer{CustomerName = "Customer 2" ,Id = 5,},
new Customer{CustomerName = "Customer 2" ,Id = 5,},
new Customer{CustomerName = "Customer 2" ,Id = 5,},
};
var customerList = customers.Distinct(new ItemEqualityComparer<Customer>(nameof(Customer.Id))).ToList();
этот код удаляет повторяющиеся элементы с помощью Id, если вы хотите удалить повторяющиеся элементы с помощью другого свойства, вы можете изменить nameof(YourClass.DuplicateProperty) на тот же nameof(Customer.CustomerName), а затем удалить повторяющиеся элементы с помощью свойства CustomerName.
Это берет отдельные (элементы без дублирующих элементов) и снова преобразует их в список:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Все ответы копируют списки, или создают новый список, или используют медленные функции, или просто очень медленные.
Насколько я понимаю, это самый быстрый и дешевый способ, который я знаю (также поддерживаемый очень опытным программистом, специализирующимся на оптимизации физики в реальном времени).
// Duplicates will be noticed after a sort O(nLogn)
list.Sort();
// Store the current and last items. Current item declaration is not really needed, and probably optimized by the compiler, but in case it's not...
int lastItem = -1;
int currItem = -1;
int size = list.Count;
// Store the index pointing to the last item we want to keep in the list
int last = size - 1;
// Travel the items from last to first O(n)
for (int i = last; i >= 0; --i)
{
currItem = list[i];
// If this item was the same as the previous one, we don't want it
if (currItem == lastItem)
{
// Overwrite last in current place. It is a swap but we don't need the last
list[i] = list[last];
// Reduce the last index, we don't want that one anymore
last--;
}
// A new item, we store it and continue
else
lastItem = currItem;
}
// We now have an unsorted list with the duplicates at the end.
// Remove the last items just once
list.RemoveRange(last + 1, size - last - 1);
// Sort again O(n logn)
list.Sort();
Окончательная стоимость:
nlogn + n + nlogn = n + 2nlogn = O (nlogn), что довольно приятно.
Примечание о RemoveRange: Поскольку мы не можем установить счетчик в списке и избежать использования функций удаления, я точно не знаю скорость этой операции, но я думаю, что это самый быстрый способ.
Думаю, самый простой способ:
Создайте новый список и добавьте уникальный элемент.
Пример:
class MyList{
int id;
string date;
string email;
}
List<MyList> ml = new Mylist();
ml.Add(new MyList(){
id = 1;
date = "2020/09/06";
email = "zarezadeh@gmailcom"
});
ml.Add(new MyList(){
id = 2;
date = "2020/09/01";
email = "zarezadeh@gmailcom"
});
List<MyList> New_ml = new Mylist();
foreach (var item in ml)
{
if (New_ml.Where(w => w.email == item.email).SingleOrDefault() == null)
{
New_ml.Add(new MyList()
{
id = item.id,
date = item.date,
email = item.email
});
}
}
Вы заботитесь о порядке элементов в результате? Это исключит некоторые решения.