Hapus duplikat dari Daftar <T> di C #

487

Adakah yang punya metode cepat untuk menghapus duplikat Daftar generik dalam C #?

JC Grubbs
sumber
4
Apakah Anda peduli dengan urutan elemen dalam hasilnya? Ini akan mengecualikan beberapa solusi.
Kolonel Panic
Solusi satu baris:ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse

Jawaban:

227

Mungkin Anda harus mempertimbangkan menggunakan HashSet .

Dari tautan 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 }
 */
Jason Baker
sumber
11
itu luar biasa cepat ... 100.000 string dengan List membutuhkan 400s dan ram 8MB, solusi saya sendiri mengambil 2,5s dan 28MB, hashset membutuhkan 0,1s !!! dan ram 11MB
sasjaq
3
HashSet tidak memiliki indeks , karena itu tidak selalu memungkinkan untuk menggunakannya. Saya harus membuat daftar besar sekali tanpa duplikat dan kemudian menggunakannya untuk ListViewdalam mode virtual. Itu sangat cepat untuk membuat yang HashSet<>pertama dan kemudian mengubahnya menjadi List<>(sehingga ListViewdapat mengakses item dengan indeks). List<>.Contains()terlalu lambat.
Sinatr
58
Akan membantu jika ada contoh cara menggunakan hashset dalam konteks khusus ini.
Nathan McKaskle
23
Bagaimana ini dapat dianggap sebagai jawaban? Ini tautan
mcont
2
HashSet sangat bagus di sebagian besar keadaan. Tetapi jika Anda memiliki objek seperti DateTime, ia membandingkan dengan referensi dan bukan dengan nilai, sehingga Anda akan tetap memiliki duplikat.
Jason McKindly
813

Jika Anda menggunakan .Net 3+, Anda dapat menggunakan Linq.

List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Faktor Mystic
sumber
14
Kode itu akan gagal ketika .Distinct () mengembalikan IEnumerable <T>. Anda harus menambahkan .ToList () ke dalamnya.
ljs
Pendekatan ini hanya dapat digunakan untuk daftar dengan nilai sederhana.
Polaris
20
Tidak, ini berfungsi dengan daftar yang berisi objek jenis apa pun. Tetapi Anda harus mengganti pembanding default untuk jenis Anda. Seperti itu: public override bool equals (object obj) {...}
BaBu
1
Itu selalu merupakan ide yang baik untuk mengganti ToString () dan GetHashCode () dengan kelas Anda sehingga hal semacam ini akan berhasil.
B Seven
2
Anda juga dapat menggunakan paket MoreLinQ Nuget yang memiliki metode ekstensi .DistinctBy (). Cukup bermanfaat.
yu_ominae
178

Bagaimana tentang:

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

Di .net 3.5?

ls
sumber
Apakah itu menggandakan daftar?
darkgaze
1
@darkgaze ini hanya membuat daftar lain dengan hanya entri unik. Jadi setiap duplikat akan dihapus dan Anda memiliki daftar di mana setiap posisi memiliki objek yang berbeda.
hexagod
Apakah ini berfungsi untuk daftar daftar item daftar di mana kode item duplikat dan perlu mendapatkan daftar unik
venkat
90

Cukup inisialisasi HashSet dengan Daftar dengan tipe yang sama:

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

Atau, jika Anda ingin Daftar dikembalikan:

var noDupsList = new HashSet<T>(withDupes).ToList();
Bahkan Mien
sumber
3
... dan jika Anda perlu List<T>menggunakan hasilnyanew HashSet<T>(withDupes).ToList()
Tim Schmelter
47

Sortir, lalu centang dua dan dua di samping satu sama lain, karena duplikat akan mengumpul.

Sesuatu seperti ini:

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

Catatan:

  • Perbandingan dilakukan dari belakang ke depan, untuk menghindari keharusan untuk menggunakan daftar setelah setiap penghapusan
  • Contoh ini sekarang menggunakan C # Value Tuples untuk melakukan swapping, ganti dengan kode yang sesuai jika Anda tidak dapat menggunakannya
  • Hasil akhirnya tidak lagi diurutkan
Lasse V. Karlsen
sumber
1
Jika saya tidak salah, sebagian besar pendekatan yang disebutkan di atas hanyalah abstraksi dari rutinitas ini, bukan? Saya akan mengambil pendekatan Anda di sini, Lasse, karena itu bagaimana saya membayangkan secara mental bergerak melalui data. Tapi, sekarang saya tertarik pada perbedaan kinerja antara beberapa saran.
Ian Patrick Hughes
7
Menerapkannya dan mengatur waktunya, hanya cara untuk memastikannya. Bahkan notasi Big-O tidak akan membantu Anda dengan metrik kinerja aktual, hanya hubungan efek pertumbuhan.
Lasse V. Karlsen
1
Saya suka pendekatan ini, lebih mudah dibawa ke bahasa lain.
Jerry Liang
10
Jangan lakukan itu. Sangat lambat. RemoveAtadalah operasi yang sangat mahal padaList
Clément
1
Clément benar. Cara untuk menyelamatkan ini adalah dengan membungkusnya dalam metode yang menghasilkan dengan enumerator dan hanya mengembalikan nilai yang berbeda. Atau Anda dapat menyalin nilai ke array atau daftar baru.
JHubbard80
33

Saya suka menggunakan perintah ini:

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

Saya memiliki bidang ini dalam daftar saya: Id, StoreName, City, PostalCode. Saya ingin menampilkan daftar kota dalam dropdown yang memiliki nilai duplikat. solusi: Kelompokkan menurut kota lalu pilih yang pertama untuk daftar.

Saya harap ini membantu :)

Eric
sumber
31

Ini berhasil untuk saya. cukup gunakan

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

Ganti "Ketik" dengan jenis yang Anda inginkan misalnya int.

Hossein Sarshar
sumber
1
Perbedaannya adalah dalam Linq, bukan System.Collections.Generic seperti yang dilaporkan oleh halaman MSDN.
Almo
5
Jawaban ini (2012) tampaknya sama dengan dua jawaban lain pada halaman ini yang berasal dari 2008?
Jon Schneider
23

Seperti kata kronoz dalam .Net 3.5 Anda dapat menggunakan Distinct().

Di .Net 2 Anda bisa menirunya:

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

Ini dapat digunakan untuk menyimpulkan koleksi apa pun dan akan mengembalikan nilai dalam urutan asli.

Biasanya lebih cepat untuk memfilter koleksi (seperti keduanya Distinct()dan sampel ini) daripada menghapus item dari itu.

Keith
sumber
Masalah dengan pendekatan ini adalah O (N ^ 2) -ish, bukan hashset. Tapi setidaknya itu jelas apa yang dilakukannya.
Tamas Czinege
1
@DrJokepu - sebenarnya saya tidak menyadari bahwa HashSetkonstruktor terputus, yang membuatnya lebih baik untuk sebagian besar keadaan. Namun, ini akan mempertahankan urutan, yang HashSettidak.
Keith
1
HashSet <T> diperkenalkan pada 3,5
thorn̈
1
@ benar-benar? Sangat sulit untuk dilacak. Dalam hal ini Anda bisa menggunakan saja sebagai Dictionary<T, object>gantinya, ganti .Containsdengan.ContainsKey dan .Add(item)dengan.Add(item, null)
Keith
@Keith, sesuai pengujian saya HashSetmempertahankan pesanan sementara Distinct()tidak.
Dennis T --Reinstate Monica--
13

Metode ekstensi mungkin cara yang layak untuk dilakukan ... sesuatu seperti ini:

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

Dan kemudian panggil seperti ini, misalnya:

List<int> myFilteredList = unfilteredList.Deduplicate();
Geoff Taylor
sumber
11

Di Jawa (saya berasumsi C # kurang lebih identik):

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

Jika Anda benar-benar ingin mengubah daftar asli:

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

Untuk mempertahankan pesanan, cukup ganti HashSet dengan LinkedHashSet.

Tom Hawtin - tackline
sumber
5
di C # akan menjadi: Daftar <T> noDupes = Daftar baru <T> (HashSet baru <T> (daftar)); daftar. Jelas (); list.AddRange (noDupes);
smohamed
Dalam C #, lebih mudah seperti ini: var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal
10

Ini membutuhkan elemen yang berbeda (elemen tanpa elemen duplikat) dan mengubahnya menjadi daftar lagi:

List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
Alfred Udah
sumber
9

Gunakan metode Union Linq .

Catatan: Solusi ini tidak memerlukan pengetahuan tentang Linq, selain itu ia ada.

Kode

Mulailah dengan menambahkan berikut ini ke bagian atas file kelas Anda:

using System.Linq;

Sekarang, Anda dapat menggunakan berikut untuk menghapus duplikat dari obyek disebut, obj1:

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

Catatan: Ganti nama obj1menjadi nama objek Anda.

Bagaimana itu bekerja

  1. Perintah Union mencantumkan satu dari setiap entri dari dua objek sumber. Karena obj1 adalah kedua objek sumber, ini mengurangi obj1 ke salah satu dari setiap entri.

  2. The ToList()mengembalikan Daftar baru. Ini diperlukan, karena perintah Linq seperti Unionmengembalikan hasil sebagai hasil IEnumerable alih-alih memodifikasi Daftar asli atau mengembalikan Daftar baru.

WonderWorker
sumber
7

Sebagai metode pembantu (tanpa Linq):

public static List<T> Distinct<T>(this List<T> list)
{
    return (new HashSet<T>(list)).ToList();
}
Hibah
sumber
Saya pikir Distinct sudah diambil. Terlepas dari itu (jika Anda mengganti nama metode) itu harus berfungsi.
Andreas Reiff
6

Jika Anda tidak peduli tentang pesanan Anda hanya bisa mendorong item ke dalam HashSet, jika Anda tidak ingin mempertahankan urutan Anda dapat melakukan sesuatu seperti ini:

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

Atau cara Linq:

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

Edit: The HashSetmetode adalah O(N)waktu dan O(N)ruang sambil memilah dan kemudian membuat unik (seperti yang disarankan oleh @ lassevk dan lain-lain) adalah O(N*lgN)waktu dan O(1)ruang sehingga tidak begitu jelas bagi saya (seperti pada pandangan pertama) bahwa cara menyortir lebih rendah (saya permintaan maaf untuk suara turun sementara ...)

Motti
sumber
6

Berikut adalah metode ekstensi untuk menghapus duplikat yang berdekatan di tempat. Panggil Sortir () terlebih dahulu dan berikan IComparer yang sama. Ini harus lebih efisien daripada versi Lasse V. Karlsen yang memanggil RemoveAt berulang kali (menghasilkan beberapa blok memori bergerak).

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);
}
gary
sumber
5

Menginstal paket MoreLINQ melalui Nuget, Anda dapat dengan mudah membedakan daftar objek dengan properti

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

Mungkin lebih mudah untuk memastikan bahwa duplikat tidak ditambahkan ke daftar.

if(items.IndexOf(new_item) < 0) 
    items.add(new_item)
Chris
sumber
1
Saat ini saya melakukannya seperti ini, tetapi semakin banyak entri yang Anda miliki, semakin lama cek untuk duplikat.
Robert Strauch
Saya memiliki masalah yang sama di sini. Saya menggunakan List<T>.Containsmetode ini setiap kali tetapi dengan lebih dari 1.000.000 entri. Proses ini memperlambat aplikasi saya. Saya menggunakan yang List<T>.Distinct().ToList<T>()pertama sebagai gantinya.
RPDeshaies
Metode ini sangat lambat
darkgaze
3

Anda bisa menggunakan Union

obj2 = obj1.Union(obj1).ToList();
flagamba
sumber
7
Penjelasan mengapa ini akan berhasil pasti akan membuat jawaban ini lebih baik
Igor B
2

Cara lain di .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();
    }
Bhasin
sumber
2

Ada banyak cara untuk menyelesaikan - masalah duplikat dalam Daftar, di bawah ini adalah salah satunya:

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

Ceria Ravi Ganesan

Ravi Ganesan
sumber
2

Berikut adalah solusi sederhana yang tidak memerlukan LINQ yang sulit dibaca atau penyortiran daftar sebelumnya.

   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
                }
            }
        }
    }
David J.
sumber
Anda memiliki kontrol lebih besar pada item yang digandakan dengan metode ini. Terlebih lagi jika Anda memiliki basis data untuk diperbarui. Untuk innerIndex, mengapa tidak memulai dari outerIndex + 1 alih-alih mulai dari awal setiap waktu?
Nolmë Informatique
2

Jawaban David J. adalah metode yang baik, tidak perlu objek tambahan, penyortiran, dll. Namun dapat diperbaiki:

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

Jadi loop luar berada di bagian bawah atas untuk seluruh daftar, tetapi loop bagian dalam pergi ke bawah "sampai posisi loop luar tercapai".

Loop luar memastikan seluruh daftar diproses, loop dalam menemukan duplikat yang sebenarnya, itu hanya dapat terjadi di bagian yang loop belum diproses.

Atau jika Anda tidak ingin melakukan bottom up untuk loop dalam, Anda bisa memulai loop dalam di luarIndex + 1.

Tamu
sumber
2

Semua jawaban menyalin daftar, atau membuat daftar baru, atau menggunakan fungsi lambat, atau lambat sekali.

Menurut pemahaman saya, ini adalah metode tercepat dan termurah yang saya tahu (juga, didukung oleh seorang programmer yang sangat berpengalaman yang berspesialisasi pada optimasi fisika waktu nyata).

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

Biaya akhir adalah:

nlogn + n + nlogn = n + 2nlogn = O (nlogn) yang cukup bagus.

Catatan tentang RemoveRange: Karena kita tidak dapat menetapkan hitungan daftar dan menghindari menggunakan fungsi Hapus, saya tidak tahu persis kecepatan operasi ini, tetapi saya kira itu adalah cara tercepat.

darkgaze
sumber
2

Jika Anda memiliki kelas derek Productdan Customerdan kami ingin menghapus item duplikat dari daftar mereka

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

}

Anda harus mendefinisikan kelas generik dalam formulir di bawah ini

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

kemudian, Anda dapat menghapus item duplikat di daftar Anda.

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

kode ini menghapus item duplikat dengan Idjika Anda ingin menghapus item duplikat oleh properti lain, Anda dapat mengubah yang nameof(YourClass.DuplicateProperty) sama nameof(Customer.CustomerName)lalu menghapus item duplikat oleh CustomerNameProperti.

Reza Jenabi
sumber
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++;
        }
     }  
  }
Paul Richards
sumber
1

Implementasi intuitif sederhana:

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;
    }
Moctar Haiz
sumber
Metode ini lambat juga. Membuat daftar baru.
darkgaze