Domanda

Qualcuno ha un metodo rapido per deduplicare un elenco generico in C#?

È stato utile?

Soluzione

Forse dovresti considerare l'utilizzo di a 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 }
 */

Altri suggerimenti

Se utilizzi .Net 3+, puoi utilizzare Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();

Che ne dite di:-

var noDupes = list.Distinct().ToList();

In .net 3.5?

Inizializza semplicemente un HashSet con un elenco dello stesso tipo:

var noDupes = new HashSet<T>(withDupes);

Oppure, se vuoi che venga restituito un elenco:

var noDupsList = new HashSet<T>(withDupes).ToList();

Ordinalo, quindi seleziona due più 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:

  • Il confronto viene effettuato da dietro in avanti, per evitare di dover ricorrere all'elenco dopo ogni rimozione
  • In questo esempio ora vengono usate le tuple dei valori C# per eseguire lo scambio, sostituirle con il codice appropriato se non è possibile utilizzarlo
  • Il risultato finale non è più ordinato

Ha funzionato per me.semplicemente usare

List<Type> liIDs = liIDs.Distinct().ToList<Type>();

Sostituisci "Tipo" con il tipo desiderato, ad es.int.

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, Città, Ccode postale I volevo mostrare l'elenco delle città in un discesa che ha valori duplicati.soluzione:Raggruppa per città, quindi scegli la prima dall'elenco.

Spero possa essere d'aiuto :)

Come ha detto 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 utilizzato per deduplicare qualsiasi raccolta e restituirà i valori nell'ordine originale.

Normalmente è molto più veloce filtrare una raccolta (poiché entrambi Distinct() e questo esempio lo fa) rispetto a rimuovere elementi da esso.

Un metodo di estensione potrebbe essere un modo decente per procedere...qualcosa come questo:

public static List<T> Deduplicate<T>(this List<T> listToDeduplicate)
{
    return listToDeduplicate.Distinct().ToList();
}

E poi chiama così, ad esempio:

List<int> myFilteredList = unfilteredList.Deduplicate();

In Java (presumo che C# sia più o meno identico):

list = new ArrayList<T>(new HashSet<T>(list))

Se volessi davvero modificare l'elenco originale:

List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);

Per preservare l'ordine, sostituisci semplicemente HashSet con LinkedHashSet.

Usa Linq Unione metodo.

Nota:Questa soluzione non richiede alcuna conoscenza di Linq, a parte il fatto che esiste.

Codice

Inizia aggiungendo quanto segue all'inizio del file della classe:

using System.Linq;

Ora puoi utilizzare quanto segue per rimuovere i duplicati da un oggetto chiamato, obj1:

obj1 = obj1.Union(obj1).ToList();

Nota:Rinominare obj1 al nome del tuo oggetto.

Come funziona

  1. Il comando Union elenca una di ciascuna voce di due oggetti di origine.Poiché obj1 è entrambi gli oggetti di origine, ciò riduce obj1 a una di ciascuna voce.

  2. IL ToList() restituisce un nuovo elenco.Questo è necessario, perché Linq comanda come Union restituisce il risultato come risultato IEnumerable invece di modificare l'elenco originale o restituire un nuovo elenco.

Se non ti interessa l'ordine puoi semplicemente inserire gli articoli in a HashSet, se tu Fare vuoi mantenere l'ordine puoi fare qualcosa del genere:

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) );

Modificare: IL HashSet il metodo è O(N) tempo e O(N) spazio mentre si ordina e poi si rende unico (come suggerito da @lassevk e altri) lo è O(N*lgN) tempo e O(1) spazio quindi non mi è così chiaro (come lo era a prima vista) che il modo di ordinamento sia inferiore (mi scuso per il temporaneo voto negativo...)

Ecco un metodo di estensione per rimuovere i duplicati adiacenti in situ.Chiama prima Sort() e passa lo stesso IComparer.Questo dovrebbe essere più efficiente di Lasse V.La versione di Karlsen che chiama RemoveAt ripetutamente (con conseguente spostamento di memoria a blocchi multipli).

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);
}

Come metodo di supporto (senza Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}

Installazione di AltroLINQ package tramite Nuget, puoi facilmente distinguere l'elenco di oggetti in base a una proprietà

IEnumerable<Catalogue> distinctCatalogues = catalogues.DistinctBy(c => c.CatalogueCode); 

Questo prende distinti (gli elementi senza duplicare gli elementi) e li converte nuovamente in un elenco:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();

Potrebbe essere più semplice assicurarsi semplicemente che i duplicati non vengano aggiunti all'elenco.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)

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 risolverlo: il problema dei duplicati nell'elenco, di seguito è riportato 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 di difficile lettura o alcun ordinamento preliminare 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 sono necessari oggetti aggiuntivi, ordinamento, ecc.Si può comunque migliorare:

for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)

Quindi il ciclo esterno va dall'alto in basso per l'intero elenco, ma il ciclo interno va in basso "finché non viene raggiunta la posizione del ciclo esterno".

Il ciclo esterno si assicura che l'intero elenco venga elaborato, il ciclo interno trova i duplicati effettivi, questi possono verificarsi solo nella parte che il ciclo esterno non ha ancora elaborato.

Oppure, se non vuoi eseguire dal basso verso l'alto per il ciclo interno, potresti fare in modo che il ciclo interno inizi da outsideIndex + 1.

Puoi usare Unione

obj2 = obj1.Union(obj1).ToList();

Se hai due lezioni Product E Customer e vogliamo rimuovere gli 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 gli elementi duplicati nel 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 da Id se desideri rimuovere gli elementi duplicati da un'altra proprietà, puoi modificare nameof(YourClass.DuplicateProperty) Stesso nameof(Customer.CustomerName) quindi rimuovi gli elementi duplicati tramite 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;
    }

Tutte le risposte copiano elenchi, creano un nuovo elenco, utilizzano funzioni lente o sono semplicemente dolorosamente lente.

Per quanto mi risulta, questo è il metodo più veloce ed economico Lo so (anche supportato da un programmatore molto esperto 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(logn) il che è piuttosto carino.

Nota su RemoveRange:Dato che non possiamo impostare il conteggio della lista ed evitare di utilizzare la funzione Rimuovi, non conosco esattamente la velocità di questa operazione ma immagino sia la via più veloce.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top