누구나 C #에서 일반 목록을 중복 제거하는 빠른 방법이 있습니까?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
누구나 C #에서 일반 목록을 중복 제거하는 빠른 방법이 있습니까?
ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
답변:
아마도 HashSet 사용을 고려해야합니다 .
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 }
*/
.Net 3 이상을 사용하는 경우 Linq를 사용할 수 있습니다.
List<T> withDupes = LoadSomeData();
List<T> noDupes = withDupes.Distinct().ToList();
동일한 유형의 List로 HashSet을 초기화하기 만하면됩니다.
var noDupes = new HashSet<T>(withDupes);
또는 List를 반환하려는 경우 :
var noDupsList = new HashSet<T>(withDupes).ToList();
List<T>
결과적으로 필요한 경우new HashSet<T>(withDupes).ToList()
그것을 정렬 한 다음 서로 옆에 두 개와 두 개를 확인하십시오. 복제물이 함께 모이기 때문입니다.
이 같은:
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--;
}
노트:
RemoveAt
에 비용이 많이 드는 작업입니다List
이 명령을 사용하고 싶습니다 :
List<Store> myStoreList = Service.GetStoreListbyProvince(provinceId)
.GroupBy(s => s.City)
.Select(grp => grp.FirstOrDefault())
.OrderBy(s => s.City)
.ToList();
내 목록에 Id, StoreName, City, PostalCode 필드가 있습니다. 중복 된 값을 가진 드롭 다운에 도시 목록을 표시하고 싶습니다. 해결책 : 도시별로 그룹화 한 다음 목록에서 첫 번째 도시를 선택하십시오.
나는 그것이 도움이되기를 바랍니다 :)
그것은 나를 위해 일했다. 단순히 사용
List<Type> liIDs = liIDs.Distinct().ToList<Type>();
"Type"을 원하는 유형으로 바꾸십시오 (예 : int).
kronoz가 .Net 3.5에서 말했듯이 사용할 수 있습니다 Distinct()
.
.Net 2에서는 다음을 모방 할 수 있습니다.
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;
}
컬렉션을 중복 제거하는 데 사용될 수 있으며 원래 순서대로 값을 반환합니다.
일반적으로 컬렉션 Distinct()
에서 항목을 제거하는 것보다 컬렉션을 필터링하는 것이 훨씬 빠릅니다 ( 이 샘플과 마찬가지로).
HashSet
생성자가 중복 제거되었음을 알지 못하여 대부분의 환경에서 더 좋습니다. 그러나 이것은 정렬 순서를 유지하지만 HashSet
그렇지 않습니다.
Dictionary<T, object>
대신 교체 .Contains
로 .ContainsKey
와 .Add(item)
함께.Add(item, null)
HashSet
주문을 유지하지만 Distinct()
그렇지 않습니다.
Java에서 (C #이 다소 동일하다고 가정) :
list = new ArrayList<T>(new HashSet<T>(list))
원래 목록을 실제로 변경하려면 다음을 수행하십시오.
List<T> noDupes = new ArrayList<T>(new HashSet<T>(list));
list.clear();
list.addAll(noDupes);
순서를 유지하려면 간단히 HashSet을 LinkedHashSet으로 바꾸십시오.
var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);
:)
Linq의 Union 방법을 사용하십시오 .
참고 :이 솔루션에는 Linq에 대한 지식이 필요합니다.
암호
클래스 파일 맨 위에 다음을 추가하여 시작하십시오.
using System.Linq;
이제 다음을 사용하여이라는 객체에서 중복을 제거 할 수 있습니다 obj1
.
obj1 = obj1.Union(obj1).ToList();
참고 : obj1
개체 이름을 바꾸 십시오.
작동 원리
Union 명령은 두 소스 개체의 각 항목 중 하나를 나열합니다. obj1은 모두 소스 객체이므로 obj1을 각 항목 중 하나로 줄입니다.
는 ToList()
새로운 목록을 반환합니다. Linq 명령과 같은 Linq 명령 Union
은 원래 List를 수정하거나 새 List를 반환하는 대신 IEnumerable 결과로 결과를 반환 하기 때문에 필요 합니다.
(Linq없이) 도우미 방법으로 :
public static List<T> Distinct<T>(this List<T> list)
{
return (new HashSet<T>(list)).ToList();
}
당신이 순서에 대한 상관 없어 경우에 당신은 단지에 항목을 밀어 수 있습니다 HashSet
당신이 경우, 않습니다 당신이 뭔가를 할 수있는 질서를 유지하려면 :
var unique = new List<T>();
var hs = new HashSet<T>();
foreach (T t in list)
if (hs.Add(t))
unique.Add(t);
또는 Linq 방식 :
var hs = new HashSet<T>();
list.All( x => hs.Add(x) );
편집 :HashSet
방법은 O(N)
시간과 O(N)
정렬하는 동안 공간 (@ 의해 제안 후 독특한 만드는 lassevk 등)입니다 O(N*lgN)
시간과 O(1)
정렬 방법은 열등한 것을 (먼저 눈에 있었다으로) 그렇게 나에게 분명하지 않도록 공간 (내 일시적 다운 투표에 대한 사과 ...)
인접한 복제본을 제자리에서 제거하는 확장 방법이 있습니다. 먼저 Sort ()를 호출하고 동일한 IComparer를 전달하십시오. 이것은 Lasse V. Karlsen의 버전보다 효율적이어야합니다.이 버전은 RemoveAt를 반복적으로 호출합니다 (여러 블록 메모리 이동이 발생 함).
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);
}
중복이 목록에 추가되지 않도록하는 것이 더 쉬울 수 있습니다.
if(items.IndexOf(new_item) < 0)
items.add(new_item)
List<T>.Contains
매번 이 방법을 사용하고 있지만 1,000,000 개 이상의 항목이 있습니다. 이 프로세스는 내 응용 프로그램을 느리게합니다. List<T>.Distinct().ToList<T>()
대신 첫 번째를 사용하고 있습니다.
.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();
}
해결 방법에는 여러 가지가 있습니다. 목록의 중복 문제는 다음 중 하나입니다.
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);
}
}
건배 라비 가네 산
다음은 읽기 어려운 LINQ 또는 사전 정렬 목록이 필요없는 간단한 솔루션입니다.
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.의 답변은 좋은 방법이며 추가 객체, 정렬 등이 필요하지 않습니다. 그러나 다음과 같이 개선 될 수 있습니다.
for (int innerIndex = items.Count - 1; innerIndex > outerIndex ; innerIndex--)
따라서 외부 루프는 전체 목록에서 맨 아래로 이동하지만 내부 루프는 "외부 루프 위치에 도달 할 때까지"맨 아래로 이동합니다.
외부 루프는 전체 목록이 처리되도록하고 내부 루프는 실제 중복을 찾고 외부 루프가 아직 처리하지 않은 부분에서만 발생할 수 있습니다.
또는 내부 루프에 대해 상향식을 수행하지 않으려면 outerIndex + 1에서 내부 루프를 시작할 수 있습니다.
모든 답변은 목록을 복사하거나 새 목록을 만들거나 느린 기능을 사용하거나 고통스럽게 느립니다.
내가 알기로는 이것이 내가 알고 있는 가장 빠르고 저렴한 방법입니다 (실시간 물리 최적화를 전문으로하는 숙련 된 프로그래머가 지원합니다).
// 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();
최종 비용은 다음과 같습니다
nlogn + n + nlogn = n + 2nlogn = O (nlogn) 이것은 꽤 좋습니다.
RemoveRange에 대한 참고 사항 : 목록 수를 설정하고 Remove 기능을 사용하지 않기 때문에이 작업의 속도를 정확히 알지 못하지만 가장 빠른 방법이라고 생각합니다.
견인 클래스가 Product
있고 Customer
목록에서 중복 항목을 제거하려는 경우
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; }
}
아래 형식으로 일반 클래스를 정의해야합니다
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();
}
}
그런 다음 목록에서 중복 항목을 제거 할 수 있습니다.
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();
이 코드 Id
는 다른 속성으로 중복 항목을 제거하려는 경우 중복 항목 을 제거하여 nameof(YourClass.DuplicateProperty)
동일하게 변경 nameof(Customer.CustomerName)
한 다음 CustomerName
속성 별로 중복 항목을 제거 할 수 있습니다 .
간단한 직관적 인 구현 :
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;
}