Adakah yang punya metode cepat untuk menghapus duplikat Daftar generik dalam C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Adakah yang punya metode cepat untuk menghapus duplikat Daftar generik dalam C #?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Jawaban:
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 }
*/
HashSet
tidak memiliki indeks , karena itu tidak selalu memungkinkan untuk menggunakannya. Saya harus membuat daftar besar sekali tanpa duplikat dan kemudian menggunakannya untuk ListView
dalam mode virtual. Itu sangat cepat untuk membuat yang HashSet<>
pertama dan kemudian mengubahnya menjadi List<>
(sehingga ListView
dapat mengakses item dengan indeks). List<>.Contains()
terlalu lambat.
Jika Anda menggunakan .Net 3+, Anda dapat menggunakan Linq.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
Bagaimana tentang:
var noDupes = list.Distinct().ToList();
Di .net 3.5?
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();
List<T>
menggunakan hasilnyanew HashSet<T>(withDupes).ToList()
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:
RemoveAt
adalah operasi yang sangat mahal padaList
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 :)
Ini berhasil untuk saya. cukup gunakan
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
Ganti "Ketik" dengan jenis yang Anda inginkan misalnya int.
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.
HashSet
konstruktor terputus, yang membuatnya lebih baik untuk sebagian besar keadaan. Namun, ini akan mempertahankan urutan, yang HashSet
tidak.
Dictionary<T, object>
gantinya, ganti .Contains
dengan.ContainsKey
dan .Add(item)
dengan.Add(item, null)
HashSet
mempertahankan pesanan sementara Distinct()
tidak.
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();
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.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Ini membutuhkan elemen yang berbeda (elemen tanpa elemen duplikat) dan mengubahnya menjadi daftar lagi:
List<type> myNoneDuplicateValue = listValueWithDuplicate.Distinct().ToList();
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 obj1
menjadi nama objek Anda.
Bagaimana itu bekerja
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.
The ToList()
mengembalikan Daftar baru. Ini diperlukan, karena perintah Linq seperti Union
mengembalikan hasil sebagai hasil IEnumerable alih-alih memodifikasi Daftar asli atau mengembalikan Daftar baru.
Sebagai metode pembantu (tanpa Linq):
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
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 HashSet
metode 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 ...)
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);
}
Mungkin lebih mudah untuk memastikan bahwa duplikat tidak ditambahkan ke daftar.
if(items.IndexOf(new_item) < 0)
items.add(new_item)
List<T>.Contains
metode 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.
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();
}
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
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
}
}
}
}
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.
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.
Jika Anda memiliki kelas derek Product
dan Customer
dan 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 Id
jika Anda ingin menghapus item duplikat oleh properti lain, Anda dapat mengubah yang nameof(YourClass.DuplicateProperty)
sama nameof(Customer.CustomerName)
lalu menghapus item duplikat oleh CustomerName
Properti.
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++;
}
}
}
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;
}