Czy ktoś ma szybką metodę usuwania duplikatów ogólnej listy w C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Czy ktoś ma szybką metodę usuwania duplikatów ogólnej listy w C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Odpowiedzi:
Być może powinieneś rozważyć użycie HashSet .
Z linku 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
nie ma indeksu , dlatego nie zawsze można go używać. Raz muszę stworzyć ogromną listę bez duplikatów, a następnie użyć jej ListView
w trybie wirtualnym. Bardzo szybko było zrobić HashSet<>
pierwszy, a następnie przekształcić go w List<>
(dzięki czemu ListView
można uzyskać dostęp do przedmiotów według indeksu). List<>.Contains()
jest zbyt wolny.
Jeśli używasz .Net 3+, możesz użyć Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Co powiesz na:
var noDupes = list.Distinct().ToList();
W .net 3.5?
Po prostu zainicjuj zestaw HashSet za pomocą listy tego samego typu:
var noDupes = new HashSet<T>(withDupes);
Lub, jeśli chcesz zwrócić listę:
var noDupsList = new HashSet<T>(withDupes).ToList();
List<T>
wyniku, użyjnew HashSet<T>(withDupes).ToList()
Posortuj, a następnie zaznacz dwa i dwa obok siebie, ponieważ duplikaty będą się zlepiać.
Coś takiego:
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--;
}
Uwagi:
RemoveAt
jest bardzo kosztowną operacją naList
Lubię używać tego polecenia:
List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
.GroupBy(s => s.City)
.Select(grp => grp.FirstOrDefault())
.OrderBy(s => s.City)
.ToList();
Mam na liście następujące pola: Id, StoreName, City, PostalCode Chciałem wyświetlić listę miast w menu, które ma zduplikowane wartości. rozwiązanie: Grupuj według miasta, a następnie wybierz pierwszą z listy.
Mam nadzieję, że to pomoże :)
To zadziałało dla mnie. po prostu użyj
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
Zamień „Type” na żądany typ, np. Int.
Jak powiedział kronoz w .Net 3.5, możesz używać Distinct()
.
W .Net 2 możesz to naśladować:
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;
}
Można to wykorzystać do deduplikacji dowolnej kolekcji i zwróci wartości w oryginalnej kolejności.
Zazwyczaj filtrowanie kolekcji jest znacznie szybsze (tak jak w przypadku Distinct()
tej i tej próbki), niż usuwanie jej z niej.
HashSet
konstruktor się poświęcił, co czyni go lepszym w większości przypadków. Zachowałoby to jednak porządek sortowania, czego HashSet
nie robi.
Dictionary<T, object>
zamiast wymienić .Contains
z .ContainsKey
i .Add(item)
z.Add(item, null)
HashSet
zachowuje porządek, podczas gdy Distinct()
nie.
Metoda rozszerzenia może być dobrym sposobem ... coś takiego:
public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
return listToDeduplicate.Distinct().ToList();
}
A potem zadzwoń w ten sposób, na przykład:
List<int> myFilteredList = unfilteredList.Deduplicate();
W Javie (zakładam, że C # jest mniej więcej identyczny):
list = new ArrayList<T>(new HashSet<T>(list))
Jeśli naprawdę chcesz zmutować oryginalną listę:
List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);
Aby zachować porządek, po prostu zamień HashSet na LinkedHashSet.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Spowoduje to rozróżnienie (elementy bez powielania elementów) i ponowne przekonwertowanie go na listę:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Użyj metody Union Linq .
Uwaga: To rozwiązanie nie wymaga znajomości Linq, poza tym, że istnieje.
Kod
Zacznij od dodania następujących elementów na początku pliku zajęć:
using System.Linq;
Teraz możesz użyć następujących poleceń, aby usunąć duplikaty z obiektu o nazwie obj1
:
obj1 = obj1.Union(obj1).ToList();
Uwaga: Zmień nazwę obj1
na nazwę swojego obiektu.
Jak to działa
Polecenie Union wyświetla jeden z każdego wpisu dwóch obiektów źródłowych. Ponieważ obj1 jest oboma obiektami źródłowymi, redukuje obj1 do jednego z każdego wpisu.
ToList()
Zwraca nową listę. Jest to konieczne, ponieważ polecenia Linq, takie jak Union
zwraca wynik jako wynik IEnumerable zamiast modyfikować oryginalną Listę lub zwracać nową Listę.
Jako metoda pomocnicza (bez Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
Jeśli nie dbają o porządek można po prostu wsadzić elementy do HashSet
, jeśli nie chcesz, aby utrzymać porządek można zrobić coś takiego:
var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
if (hs.Add(t))
unique.Add(t);
Lub sposób Linq:
var hs = new HashSet<T>();
list.All( x => hs.Add(x) );
Edit:HashSet
metoda jest O(N)
czas i O(N)
miejsce podczas sortowania a następnie podejmowania wyjątkowy (jak sugeruje @ lassevk i innych) jest O(N*lgN)
czas i O(1)
przestrzeń, więc to nie jest tak oczywiste dla mnie (jak to było na pierwszy rzut oka), że sposób sortowania jest gorszy (moja przepraszam za tymczasowe głosowanie w dół ...)
Oto metoda rozszerzenia służąca do usuwania sąsiadujących duplikatów na miejscu. Najpierw wywołaj Sort () i przekaż ten sam IComparer. Powinno to być bardziej wydajne niż wersja Lasse V. Karlsena, która wielokrotnie wywołuje RemoveAt (co powoduje wiele ruchów pamięci bloków).
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);
}
Łatwiej może być po prostu upewnienie się, że duplikaty nie zostaną dodane do listy.
if(items.IndexOf(new_item) < 0)
items.add(new_item)
List<T>.Contains
metody za każdym razem, ale z ponad 1 000 000 wpisów. Ten proces spowalnia moją aplikację. List<T>.Distinct().ToList<T>()
Zamiast tego używam pierwszego.
Kolejny sposób w .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();
}
Istnieje wiele sposobów rozwiązania - problem duplikatów na liście, poniżej jest jednym z nich:
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);
}
}
Pozdrawiam Ravi Ganesan
Oto proste rozwiązanie, które nie wymaga trudnego do odczytania LINQ ani żadnego wcześniejszego sortowania listy.
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
}
}
}
}
Odpowiedź Davida J. jest dobrą metodą, nie wymaga dodatkowych obiektów, sortowania itp. Można ją jednak ulepszyć:
for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)
Tak więc zewnętrzna pętla znajduje się u góry na dole dla całej listy, ale wewnętrzna pętla jest na dole „aż do osiągnięcia pozycji zewnętrznej pętli”.
Zewnętrzna pętla zapewnia, że cała lista jest przetwarzana, wewnętrzna pętla znajduje rzeczywiste duplikaty, mogą się one zdarzyć tylko w części, której zewnętrzna pętla jeszcze nie przetworzyła.
Lub jeśli nie chcesz robić oddolnej pętli wewnętrznej, możesz rozpocząć pętlę wewnętrzną od outerIndex + 1.
Wszystkie odpowiedzi kopiują listy, tworzą nową listę, używają wolnych funkcji lub są po prostu boleśnie powolne.
Według mnie jest to najszybsza i najtańsza metoda, jaką znam (wspierana przez bardzo doświadczonego programistę specjalizującego się w optymalizacji fizyki w czasie rzeczywistym).
// 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();
Ostateczny koszt to:
nlogn + n + nlogn = n + 2nlogn = O (nlogn), co jest całkiem miłe.
Uwaga na temat RemoveRange: Ponieważ nie możemy ustawić liczby na liście i uniknąć korzystania z funkcji Usuń, nie znam dokładnie szybkości tej operacji, ale myślę, że jest to najszybszy sposób.
Jeśli masz zajęcia holownicze Product
i Customer
chcemy usunąć zduplikowane elementy z ich listy
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; }
}
Musisz zdefiniować klasę ogólną w poniższym formularzu
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();
}
}
następnie możesz usunąć zduplikowane elementy z listy.
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();
ten kod usunąć zduplikowane pozycje wg Id
jeśli chcesz usunąć duplikaty przez inne właściwości, można zmienić nameof(YourClass.DuplicateProperty)
sam nameof(Customer.CustomerName)
potem usunąć duplikaty przez CustomerName
Property.
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++;
}
}
}
Prosta intuicyjna implementacja:
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;
}