C # 'da <T> Listesindeki kopyaları kaldırma


487

Herkes C # genel bir listeyi çoğaltmak için hızlı bir yöntem var mı?


4
Sonuçtaki öğelerin sırasını önemsiyor musunuz? Bu, bazı çözümleri hariç tutacaktır.
Albay Panik

Tek satırlık bir çözüm:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse

Yanıtlar:


227

Belki de bir HashSet kullanmayı düşünmelisiniz .

MSDN bağlantısından:

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 }
 */

11
onun inanılmaz hızlı ... 100.000 List listesi ile 400s ve 8MB ram alır, kendi çözümüm 2.5s ve 28MB alır, hashset 0.1s alır !!! ve
11MB

3
HashSet bir indeksi yoktur , bu nedenle kullanmak her zaman mümkün değildir. Bir kez yinelenmeden büyük bir liste oluşturmak ve sonra ListViewsanal modda kullanmak zorunda . Bir HashSet<>ilk yapmak ve daha sonra bir haline dönüştürmek süper hızlıydı List<>(böylece ListViewöğelere indeksle erişebilir). List<>.Contains()çok yavaş.
Sinatr

58
Bu bağlamda bir hashset'in nasıl kullanılacağına dair bir örnek olsaydı yardımcı olur.
Nathan McKaskle

23
Bu nasıl bir cevap olarak düşünülebilir? Bu bir bağlantı
mcont

2
HashSet çoğu durumda mükemmeldir. Ancak DateTime gibi bir nesneniz varsa, değere göre değil referansa göre karşılaştırılır, böylece yine de yinelenir.
Jason McKindly

813

Net 3+ kullanıyorsanız, Linq'i kullanabilirsiniz.

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

14
Bu kod .Distinct () bir IEnumerable <T> döndürdüğünde başarısız olur. Buna .ToList () eklemelisiniz.
ljs

Bu yaklaşım yalnızca basit değerlere sahip listeler için kullanılabilir.
Polaris

20
Hayır, herhangi bir türde nesne içeren listelerle çalışır. Ancak, türünüz için varsayılan karşılaştırıcıyı geçersiz kılmanız gerekir. Şöyle ki: genel geçersiz kılma bool Eşittir (nesne obj) {...}
BaBu

1
Sınıflarınızla ToString () ve GetHashCode () öğelerini geçersiz kılmak her zaman iyi bir fikirdir, böylece bu tür şeyler işe yarayacaktır.
B Seven

2
.DistinctBy () uzantısı yöntemi olan MoreLinQ Nuget paketini de kullanabilirsiniz. Oldukça kullanışlı.
yu_ominae

178

Nasıl olur:

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

Net 3.5 içinde mi?


Listeyi kopyalıyor mu?
darkgaze

1
@darkgaze bu yalnızca benzersiz girişleri olan başka bir liste oluşturur. Böylece, kopyalar kaldırılacak ve her konumun farklı bir nesneye sahip olduğu bir liste bırakacaksınız.
hexagod

Bu, öğe kodlarının kopya olduğu ve benzersiz bir liste alması gereken liste öğelerinin listesi için çalışır
venkat

90

Aynı türden bir Liste ile bir HashSet başlatmanız yeterlidir:

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

Veya bir Listenin döndürülmesini istiyorsanız:

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

3
... ve List<T>sonuç olarak kullanmanız gerekiyorsanew HashSet<T>(withDupes).ToList()
Tim Schmelter

47

Sıralayın, daha sonra birbirlerinin yanındaki iki ve ikiyi kontrol edin, çünkü kopyalar birbirine yapışacaktır.

Bunun gibi bir şey:

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

Notlar:

  • Her kaldırma işleminden sonra listeye başvurmak zorunda kalmamak için karşılaştırma arkadan öne doğru yapılır
  • Bu örnek artık değiştirmeyi yapmak için C # Value Tuples kullanıyor, bunu kullanamıyorsanız uygun kodla değiştirin
  • Sonuç artık sıralanmıyor

1
Yanılmıyorsam, yukarıda bahsedilen yaklaşımların çoğu sadece bu rutinin soyutlamalarıdır, değil mi? Buradaki yaklaşımınızı benimseyebilirdim, Lasse, çünkü veriler arasında hareket etmeyi zihinsel olarak nasıl hayal ettiğim. Ancak, şimdi bazı öneriler arasındaki performans farklarıyla ilgileniyorum.
Ian Patrick Hughes

7
Onları uygulayın ve zamanlayın, emin olmanın tek yolu. Big-O gösterimi bile gerçek performans metriklerinde size yardımcı olmaz, sadece bir büyüme etkisi ilişkisi.
Lasse V. Karlsen

1
Bu yaklaşımı seviyorum, diğer diller için daha taşınabilir.
Jerry Liang

10
Bunu yapma. Süper yavaş. RemoveAtbir çok pahalı bir operasyonList
Clément

1
Clément haklı. Bunu kurtarmanın bir yolu, bunu bir numaralandırıcı ile sonuçlanan ve sadece farklı değerler döndüren bir yöntemle sarmak olacaktır. Alternatif olarak değerleri yeni bir diziye veya listeye kopyalayabilirsiniz.
JHubbard80

33

Bu komutu kullanmayı seviyorum:

List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
                                                 .GroupBy(s => s.City)
                                                 .Select(grp => grp.FirstOrDefault())
                                                 .OrderBy(s => s.City)
                                                 .ToList();

Listemde şu alanlar var: Id, StoreName, City, PostalCode Yinelenen değerleri olan bir açılır menüde şehirlerin listesini göstermek istedim. çözüm: Şehre göre gruplandırın ve ardından liste için ilkini seçin.

Umut ediyorum bu yardım eder :)


31

Benim için çalıştı. sadece kullan

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

"Tür" ü istediğiniz türle değiştirin, örn. İnt.


1
MSDN sayfası tarafından bildirildiği gibi System.Collections.Generic içinde değil, Linq'dedir.
Almo

5
Bu cevap (2012), bu sayfadaki 2008'den gelen diğer iki cevapla aynı mı görünüyor?
Jon Schneider

23

Kronoz'un .Net 3.5'te dediği gibi kullanabilirsiniz Distinct().

Net 2'de bunu taklit edebilirsiniz:

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

Bu, herhangi bir koleksiyonu çıkarmak için kullanılabilir ve değerleri orijinal sırayla döndürür.

Normalde bir koleksiyonu filtrelemek (hem Distinct()bu örnekte hem de bu örnekte olduğu gibi) öğeleri kaldırmaktan çok daha hızlıdır .


Bu yaklaşımla ilgili sorun, bir hashsetin aksine O (N ^ 2) -ish olmasıdır. Ama en azından ne yaptığı belli.
Tamas Czinege

1
@DrJokepu - aslında HashSetyapıcısının tekilleştirildiğini fark etmedim, bu da çoğu koşul için daha iyi hale getiriyor. Ancak, bu sıralama düzenini koruyacaktır, ki bu da bir HashSet.
Keith

1
HashSet <T>
3.5'te

1
@thorn gerçekten mi? Takip etmek çok zor. Bu durumda sadece kullanabilirsiniz Dictionary<T, object>yerine, yerini .Containsile .ContainsKeyve .Add(item)ile.Add(item, null)
Keith

@Keith, testime göre HashSetsiparişi korurken Distinct()değil koruyor .
Dennis T - Monica'yı eski durumuna getirin

13

Bir uzatma yöntemi böyle bir şeye gitmenin iyi bir yolu olabilir:

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

Ve sonra böyle arayın, örneğin:

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

11

Java (C # aşağı yukarı aynı olduğunu varsayalım):

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

Orijinal listeyi gerçekten değiştirmek istiyorsanız:

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

Siparişi korumak için HashSet'i LinkedHashSet ile değiştirin.


5
C # 'da olurdu: Liste <T> noDupes = yeni Liste <T> (yeni HashSet <T> (liste)); list.Clear (); list.AddRange (noDupes);
smohamed

C # ile, bu şekilde daha kolay: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal

10

Bu farklı (öğeleri kopyalamayan öğeler) alır ve tekrar listeye dönüştürür:

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

9

Linq's Union yöntemini kullanın .

Not: Bu çözüm, Linq hakkında bir bilgi gerektirmez, bunun dışında.

kod

Sınıf dosyanızın üstüne aşağıdakileri ekleyerek başlayın:

using System.Linq;

Şimdi, aşağıdaki nesnelerden yinelenenleri kaldırmak için aşağıdakileri kullanabilirsiniz obj1:

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

Not: obj1Nesnenin adıyla yeniden adlandırın .

Nasıl çalışır

  1. Birlik komutu iki kaynak nesnenin her girişinden birini listeler. Obj1 her iki kaynak nesne olduğundan, obj1 her girişten birine indirgenir.

  2. ToList()Yeni bir List döndürür. Linq komutları Union, sonucu orijinal Listeyi değiştirmek veya yeni bir Liste döndürmek yerine IEnumerable sonucu olarak döndürdüğünden bu gereklidir .


7

Yardımcı bir yöntem olarak (Linq olmadan):

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

Bence Distinct zaten alınmış. Bunun dışında (yöntemi yeniden adlandırırsanız) çalışmalıdır.
Andreas Reiff

6

Siparişin umurumda değil Eğer sadece içine öğeleri dürtme HashSeteğer, do böyle bir şey yapabileceği düzeni korumak istiyorum:

var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
    if (hs.Add(t))
        unique.Add(t);

Veya Linq yolu:

var hs = new HashSet<T>();
list.All( x =>  hs.Add(x) );

Düzenleme:HashSet yöntemdir O(N)zaman ve O(N)sıralama yaparken uzay ve (@ tarafından önerildiği gibi daha sonra benzersiz hale lassevk ve diğerleri) 'dir O(N*lgN)zaman ve O(1)sıralama yolu aşağı olduğu (ilk bakışta olduğu gibi) o kadar bana açık değil bu yüzden uzay (benim geçici oylama için özür dileriz ...)


6

İşte bitişik kopyaları yerinde kaldırmak için bir genişletme yöntemi. Önce Sort () öğesini çağırın ve aynı IComparer'a geçin. Bu Lasse V. Karlsen'in RemoveAt'ı tekrar tekrar çağıran versiyonundan daha verimli olmalıdır (çoklu blok bellek hareketlerine neden olur).

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

5

Yükleme MoreLINQ bir özelliğiyle kolayca Nuget aracılığıyla farklı nesne listesini paketi olabilir

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

3

Yinelenenlerin listeye eklenmediğinden emin olmak daha kolay olabilir.

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

1
Şu anda böyle yapıyorum ama daha fazla giriş daha uzun yinelenen çek alır.
Robert Strauch

Burada da aynı problem var. List<T>.ContainsHer seferinde yöntemi kullanıyorum ancak 1.000.000'dan fazla girişle. Bu işlem başvurumu yavaşlatır. Onun List<T>.Distinct().ToList<T>()yerine bir ilk kullanıyorum.
RPDeshaies

Bu yöntem çok yavaş
darkgaze

3

Birlik kullanabilirsiniz

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

7
Neden işe yarayacağına dair açıklama bu cevabı kesinlikle daha iyi hale getirecektir
Igor B

2

Net 2.0'da başka bir yol

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

2

Çözmenin birçok yolu vardır - Listede yinelenen sorun, aşağıda bunlardan biri:

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

Şerefe Ravi Ganesan


2

İşte, okunması zor LINQ veya listenin önceden sıralanmasını gerektirmeyen basit bir çözüm.

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

Bu yöntemle çoğaltılan öğeler üzerinde daha fazla kontrole sahipsiniz. Güncellenecek bir veritabanınız varsa daha da fazlası. İnnerIndex için, neden her seferinde başlamak yerine externalIndex + 1'den başlamıyorsunuz?
Nolmë Informatique

2

David J.'nin cevabı iyi bir yöntemdir, ekstra nesnelere gerek yoktur, sıralama vb.

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

Böylece dış döngü tüm liste için en altta, ancak iç döngü "dış döngü konumuna ulaşılana kadar" aşağı iner.

Dış döngü, tüm listenin işlendiğinden emin olur, iç döngü gerçek kopyaları bulur, bunlar yalnızca dış döngünün henüz işlenmediği kısımda olabilir.

Veya iç döngü için aşağıdan yukarıya yapmak istemiyorsanız, iç döngü dışIndex + 1'den başlatabilirsiniz.


2

Tüm yanıtlar listeleri kopyalar ya da yeni bir liste oluşturur ya da yavaş işlevler kullanır ya da çok yavaştır.

Anladığım kadarıyla, bu bildiğim en hızlı ve en ucuz yöntem (ayrıca, gerçek zamanlı fizik optimizasyonu konusunda uzmanlaşmış çok deneyimli bir programcı tarafından destekleniyor).

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

Son maliyet:

nlogn + n + nlogn = n + 2nlogn = O (nlogn) ki bu oldukça hoş.

RemoveRange hakkında not: Listenin sayısını ayarlayamadığımız ve İşlevleri kaldır'ı kullanamadığımız için, bu işlemin hızını tam olarak bilmiyorum ama sanırım en hızlı yol bu.


2

Yedekte sınıflarınız varsa Productve Customeryinelenen öğeleri listelerinden kaldırmak istiyorsak

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

}

Aşağıdaki formda bir genel sınıf tanımlamanız gerekir

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

ardından listenizdeki yinelenen öğeleri kaldırabilirsiniz.

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

bu kod, yinelenen öğeleri Idbaşka bir özelliğe göre kaldırmak isterseniz, nameof(YourClass.DuplicateProperty) aynı nameof(Customer.CustomerName)öğeleri değiştirebilir ve ardından yinelenen öğeleri CustomerNameMülke göre kaldırabilirsiniz .


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

1

Basit, sezgisel bir uygulama:

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

Bu yöntem de yavaş. Yeni bir liste oluşturur.
darkgaze
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.