Qualcuno ha un metodo rapido per de-duplicare un elenco generico in C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Qualcuno ha un metodo rapido per de-duplicare un elenco generico in C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Risposte:
Forse dovresti prendere in considerazione l'utilizzo di un HashSet .
Dal collegamento 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
non ha un indice , quindi non è sempre possibile usarlo. Devo creare una volta un enorme elenco senza duplicati e quindi utilizzarlo per ListView
la modalità virtuale. È stato velocissimo fare un HashSet<>
primo e poi convertirlo in un List<>
(quindi ListView
può accedere agli oggetti per indice). List<>.Contains()
è troppo lento.
Se stai usando .Net 3+, puoi usare Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Che ne dite di:
var noDupes = list.Distinct().ToList();
In .net 3.5?
Basta inizializzare un HashSet con un elenco dello stesso tipo:
var noDupes = new HashSet<T>(withDupes);
Oppure, se si desidera che venga restituito un elenco:
var noDupsList = new HashSet<T>(withDupes).ToList();
List<T>
risultato usanew HashSet<T>(withDupes).ToList()
Ordinalo, quindi controlla due e due uno accanto all'altro, poiché i duplicati si raggrupperanno insieme.
Qualcosa come questo:
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--;
}
Appunti:
RemoveAt
è un'operazione molto costosa su unList
Mi piace usare questo comando:
List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
.GroupBy(s => s.City)
.Select(grp => grp.FirstOrDefault())
.OrderBy(s => s.City)
.ToList();
Ho questi campi nella mia lista: Id, StoreName, City, PostalCode Volevo mostrare la lista delle città in un menu a discesa che ha valori duplicati. soluzione: raggruppa per città, quindi scegli il primo per l'elenco.
Spero possa essere d'aiuto :)
Ha funzionato per me. semplicemente usa
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
Sostituisci "Tipo" con il tipo desiderato, ad es. Int.
Come diceva kronoz in .Net 3.5 puoi usare Distinct()
.
In .Net 2 potresti imitarlo:
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;
}
Questo potrebbe essere usato per deduperare qualsiasi raccolta e restituirà i valori nell'ordine originale.
Normalmente è molto più veloce filtrare una raccolta (come entrambi Distinct()
e in questo esempio) di quanto sarebbe rimuovere elementi da essa.
HashSet
costruttore ha dedotto, il che lo rende migliore per la maggior parte delle circostanze. Tuttavia, ciò preserverebbe il criterio di ordinamento, cosa HashSet
che non accade.
Dictionary<T, object>
invece, sostituiscilo .Contains
con .ContainsKey
e .Add(item)
con.Add(item, null)
HashSet
conserva l'ordine mentre Distinct()
no.
Un metodo di estensione potrebbe essere un modo decente per andare ... qualcosa del genere:
public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
return listToDeduplicate.Distinct().ToList();
}
E quindi chiama in questo modo, ad esempio:
List<int> myFilteredList = unfilteredList.Deduplicate();
In Java (suppongo che C # sia più o meno identico):
list = new ArrayList<T>(new HashSet<T>(list))
Se davvero volevi mutare la lista originale:
List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);
Per preservare l'ordine, è sufficiente sostituire HashSet con LinkedHashSet.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Questo prende distinti (gli elementi senza duplicare gli elementi) e lo converte nuovamente in un elenco:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Usa il metodo Union di Linq .
Nota: questa soluzione non richiede alcuna conoscenza di Linq, a parte il fatto che esiste.
Codice
Inizia aggiungendo quanto segue all'inizio del file di classe:
using System.Linq;
Ora è possibile utilizzare quanto segue per rimuovere i duplicati da un oggetto chiamato obj1
:
obj1 = obj1.Union(obj1).ToList();
Nota: rinominare obj1
il nome dell'oggetto.
Come funziona
Il comando Unione elenca una di ciascuna voce di due oggetti sorgente. Poiché obj1 è entrambi oggetti sorgente, ciò riduce obj1 a una di ciascuna voce.
Il ToList()
restituisce una nuova lista. Ciò è necessario, poiché Linq comanda come Union
restituisce il risultato come risultato IEnumerable invece di modificare l'elenco originale o restituire un nuovo elenco.
Come metodo di supporto (senza Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
Se non vi interessa circa l'ordine si può solo spingere gli elementi in una HashSet
, se non desidera mantenere l'ordine si può fare qualcosa di simile a questo:
var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
if (hs.Add(t))
unique.Add(t);
O il modo Linq:
var hs = new HashSet<T>();
list.All( x => hs.Add(x) );
Modifica: il HashSet
metodo è O(N)
tempo e O(N)
spazio durante l'ordinamento e quindi rendere unico (come suggerito da @ lassevk e altri) è il O(N*lgN)
tempo e lo O(1)
spazio, quindi non è così chiaro per me (come era a prima vista) che il modo di ordinamento è inferiore (il mio scuse per il voto temporaneo in giù ...)
Ecco un metodo di estensione per rimuovere duplicati adiacenti in situ. Chiama prima Sort () e passa nello stesso IComparer. Questo dovrebbe essere più efficiente della versione di Lasse V. Karlsen che chiama ripetutamente RemoveAt (con conseguente spostamento di più blocchi di memoria).
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);
}
Potrebbe essere più semplice assicurarsi che i duplicati non vengano aggiunti all'elenco.
if(items.IndexOf(new_item) < 0)
items.add(new_item)
List<T>.Contains
metodo ogni volta ma con più di 1.000.000 di voci. Questo processo rallenta la mia domanda. List<T>.Distinct().ToList<T>()
Invece sto usando un primo.
Un altro modo in .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();
}
Esistono molti modi per risolvere: il problema dei duplicati nell'elenco, di seguito è uno di questi:
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);
}
}
Saluti Ravi Ganesan
Ecco una soluzione semplice che non richiede alcun LINQ difficile da leggere o alcun ordinamento precedente dell'elenco.
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
}
}
}
}
La risposta di David J. è un buon metodo, non c'è bisogno di oggetti extra, ordinamento, ecc. Tuttavia può essere migliorato su:
for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)
Quindi il loop esterno va in fondo in alto per l'intero elenco, ma il loop interno va in fondo "fino a quando non viene raggiunta la posizione del loop esterno".
Il ciclo esterno si assicura che l'intero elenco sia elaborato, il ciclo interno trova i duplicati effettivi, questi possono accadere solo nella parte che il ciclo esterno non ha ancora elaborato.
Oppure, se non vuoi fare il bottom up per il loop interno, potresti far iniziare il loop interno da outerIndex + 1.
Tutte le risposte copiano gli elenchi o creano un nuovo elenco o utilizzano le funzioni lente o sono dolorosamente lente.
Per quanto ne so , questo è il metodo più veloce ed economico che conosco (supportato anche da un programmatore di grande esperienza specializzato nell'ottimizzazione della fisica in tempo reale).
// 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();
Il costo finale è:
nlogn + n + nlogn = n + 2nlogn = O (nlogn) che è piuttosto carino.
Nota su RemoveRange: poiché non è possibile impostare il conteggio dell'elenco ed evitare di utilizzare le funzioni Rimuovi, non conosco esattamente la velocità di questa operazione, ma immagino che sia il modo più veloce.
Se hai classi di rimorchio Product
e Customer
vogliamo rimuovere elementi duplicati dal loro elenco
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; }
}
È necessario definire una classe generica nel modulo seguente
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();
}
}
quindi, puoi rimuovere elementi duplicati dal tuo elenco.
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();
questo codice rimuove gli elementi duplicati Id
se si desidera rimuovere gli elementi duplicati da un'altra proprietà, è possibile modificare lo nameof(YourClass.DuplicateProperty)
stesso nameof(Customer.CustomerName)
quindi rimuovere gli elementi duplicati dalla CustomerName
proprietà.
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++;
}
}
}
Una semplice implementazione intuitiva:
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;
}