У кого-нибудь есть быстрый способ дедупликации универсального списка в C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
У кого-нибудь есть быстрый способ дедупликации универсального списка в C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Ответы:
Возможно, вам следует рассмотреть возможность использования 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 }
*/
HashSet
не имеет индекса , поэтому его не всегда можно использовать. Я должен создать один раз огромный список без дубликатов, а затем использовать его ListView
в виртуальном режиме. Это было очень быстро сделать HashSet<>
сначала, а затем преобразовать его в List<>
(так что ListView
можете получить доступ к элементам по индексу). List<>.Contains()
слишком медленно
Если вы используете .Net 3+, вы можете использовать Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Как насчет:
var noDupes = list.Distinct().ToList();
В .net 3.5?
Сортируйте его, затем отметьте два и два рядом друг с другом, так как дубликаты будут объединяться.
Что-то вроде этого:
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--;
}
Ноты:
RemoveAt
является очень дорогостоящей операциейList
Мне нравится использовать эту команду:
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>();
Замените «Тип» на желаемый тип, например, int.
Как сказал кроноз в .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()
и в этом примере), чем удалять из нее элементы.
HashSet
конструктор дедуплицировал, что делает его лучше для большинства обстоятельств. Тем не менее, это сохранит порядок сортировки, чего HashSet
нет.
Dictionary<T, object>
вместо, заменить .Contains
на .ContainsKey
и .Add(item)
с.Add(item, null)
HashSet
сохраняет порядок, пока Distinct()
нет.
Метод расширения может быть приличным способом ... что-то вроде этого:
public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
return listToDeduplicate.Distinct().ToList();
}
А потом позвоните вот так, например:
List<int> myFilteredList = unfilteredList.Deduplicate();
В 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.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Это берет разные (элементы без дублирующих элементов) и снова конвертирует их в список:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Используйте метод Linq's Union .
Примечание. Это решение не требует знания Linq, кроме того, что оно существует.
Код
Начните с добавления следующего в начало вашего файла класса:
using System.Linq;
Теперь вы можете использовать следующее для удаления дубликатов из объекта с именем obj1
:
obj1 = obj1.Union(obj1).ToList();
Примечание: переименуйте obj1
в название вашего объекта.
Как это работает
Команда Union перечисляет одну из каждой записи двух исходных объектов. Поскольку obj1 - оба исходных объекта, это сводит obj1 к одной из каждой записи.
ToList()
Возвращает новый список. Это необходимо, поскольку команды Linq like Union
возвращают результат в виде результата IEnumerable вместо изменения исходного списка или возврата нового списка.
В качестве вспомогательного метода (без Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
Если вы не заботитесь о порядке вы можете просто засунуть элементы в HashSet
, если вы действительно хотите сохранить заказ вы можете сделать что - то вроде этого:
var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
if (hs.Add(t))
unique.Add(t);
Или Линк путь:
var hs = new HashSet<T>();
list.All( x => hs.Add(x) );
Edit:HashSet
метод O(N)
времени и O(N)
пространства во время сортировки , а затем сделать уникальный (как это было предложено @ lassevk и другие) это O(N*lgN)
время и O(1)
пространство , так что это не так ясно для меня (как это было на первый взгляд) , что сортировка путь уступает (мой извиняюсь за временное отрицательное голосование ...)
Вот метод расширения для удаления соседних дубликатов на месте. Сначала вызовите 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);
}
Может быть проще просто убедиться, что дубликаты не добавляются в список.
if(items.IndexOf(new_item) < 0)
items.add(new_item)
List<T>.Contains
метод каждый раз, но с более чем 1 000 000 записей. Этот процесс замедляет мое приложение. Я использую List<T>.Distinct().ToList<T>()
первый вместо этого.
Еще один способ в .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();
}
Есть много способов решить проблему с дубликатами в списке, ниже один из них:
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
}
}
}
}
Ответ Дэвида Дж. - хороший метод, не требующий дополнительных объектов, сортировки и т. Д. Однако его можно улучшить:
for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)
Таким образом, внешний цикл идет сверху вниз для всего списка, но внутренний цикл идет снизу «пока не будет достигнута позиция внешнего цикла».
Внешний цикл гарантирует, что весь список обработан, внутренний цикл находит фактические дубликаты, они могут произойти только в той части, которую внешний цикл еще не обработал.
Или, если вы не хотите делать восходящий цикл для внутреннего цикла, вы можете запустить внутренний цикл в externalIndex + 1.
Все ответы копируют списки, или создают новый список, или используют медленные функции, или просто мучительно медленные.
Насколько я понимаю, это самый быстрый и самый дешевый метод, который я знаю (при поддержке очень опытного программиста, специализирующегося на оптимизации физики в реальном времени).
// 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: поскольку мы не можем установить счетчик списка и избежать использования функций удаления, я не знаю точно скорость этой операции, но я думаю, что это самый быстрый способ.
Если у вас есть классы буксирных 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
недвижимости.
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++;
}
}
}
Простая интуитивно понятная реализация:
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;
}