C #의 List <T>에서 중복 제거


487

누구나 C #에서 일반 목록을 중복 제거하는 빠른 방법이 있습니까?


4
결과의 요소 순서에 관심이 있습니까? 일부 솔루션은 제외됩니다.
Colonic Panic

한 줄 솔루션 :ICollection<MyClass> withoutDuplicates = new HashSet<MyClass>(inputList);
Harald Coppoolse at

답변:


227

아마도 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 }
 */

11
믿을 수 없을만큼 빠릅니다 ... List가있는 100.000 문자열에는 400s와 8MB 램이 필요합니다. 내 솔루션은 2.5s와 28MB, 해시 세트는 0.1s입니다! 11MB ram
sasjaq

3
HashSet index 가 없으므로 항상 사용할 수있는 것은 아닙니다. 중복없이 거대한 목록을 한 번 만든 다음 ListView가상 모드에서 사용해야 합니다. 그것은 수 있도록 초고속이었다 HashSet<>첫째을 다음으로 변환 List<>(그래서 ListView인덱스가 액세스 할 수 항목). List<>.Contains()너무 느립니다.
Sinatr

58
이 특정 컨텍스트에서 해시 세트를 사용하는 방법에 대한 예제가 있다면 도움이 될 것입니다.
Nathan McKaskle

23
이것이 어떻게 대답을 고려할 수 있습니까? 그것은 링크입니다
mcont

2
HashSet은 대부분의 환경에서 훌륭합니다. 그러나 DateTime과 같은 개체가 있으면 값이 아닌 참조로 비교되므로 여전히 중복으로 끝납니다.
Jason McKindly

813

.Net 3 이상을 사용하는 경우 Linq를 사용할 수 있습니다.

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

14
.Distinct ()가 IEnumerable <T>를 반환하면 해당 코드가 실패합니다. .ToList ()를 추가해야합니다.
ljs

이 방법은 단순한 값을 가진 목록에만 사용할 수 있습니다.
Polaris

20
아니요, 모든 유형의 객체가 포함 된 목록에서 작동합니다. 그러나 해당 유형의 기본 비교자를 재정의해야합니다. 이와 같이 : public override bool Equals (object obj) {...}
BaBu

1
이런 종류의 작업이 작동하도록 ToString () 및 GetHashCode ()를 클래스로 재정의하는 것이 좋습니다.
B Seven

2
확장명이 .DistinctBy () 인 MoreLinQ Nuget 패키지를 사용할 수도 있습니다. 꽤 유용합니다.
yu_ominae

178

어때요?

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

.net 3.5에서?


목록을 복제합니까?
darkgaze

1
@darkgaze 이것은 유일한 항목으로 다른 목록을 만듭니다. 따라서 중복 항목이 제거되고 모든 위치에 다른 개체가있는 목록이 남습니다.
hexagod

품목 코드가 중복되어 고유 한 목록을
가져와야

90

동일한 유형의 List로 HashSet을 초기화하기 만하면됩니다.

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

또는 List를 반환하려는 경우 :

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

3
... 그리고 List<T>결과적으로 필요한 경우new HashSet<T>(withDupes).ToList()
Tim Schmelter

47

그것을 정렬 한 다음 서로 옆에 두 개와 두 개를 확인하십시오. 복제물이 함께 모이기 때문입니다.

이 같은:

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

노트:

  • 각 제거 후에 목록을 작성하지 않아도되도록 뒤에서 앞으로 비교합니다.
  • 이 예제는 이제 C # Value Tuples를 사용하여 스와핑을 수행하고 사용할 수없는 경우 적절한 코드로 대체합니다.
  • 최종 결과가 더 이상 정렬되지 않습니다

1
내가 실수하지 않으면 위에서 언급 한 대부분의 접근 방식은이 일상의 추상화 일뿐입니다. 데이터를 통해 이동하는 것을 정신적으로 묘사하는 방식이기 때문에 Lasse에서 접근했습니다. 그러나 이제 일부 제안 간의 성능 차이에 관심이 있습니다.
Ian Patrick Hughes

7
그들을 구현하고 시간을 정하십시오. Big-O 표기법조차도 실제 성능 지표에 도움이되지 않고 성장 효과 관계에만 도움이됩니다.
Lasse V. Karlsen

1
나는이 접근법을 좋아한다. 다른 언어로 이식하기가 더 쉽다.
Jerry Liang

10
하지마 매우 느립니다. RemoveAt에 비용이 많이 드는 작업입니다List
Clément

1
클레멘트가 맞아 이것을 구하는 방법은 이것을 열거자를 사용하여 산출하고 구별되는 값만 리턴하는 메소드로 랩핑하는 것입니다. 또는 값을 새 배열 또는 목록에 복사 할 수 있습니다.
JHubbard80

33

이 명령을 사용하고 싶습니다 :

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

내 목록에 Id, StoreName, City, PostalCode 필드가 있습니다. 중복 된 값을 가진 드롭 다운에 도시 목록을 표시하고 싶습니다. 해결책 : 도시별로 그룹화 한 다음 목록에서 첫 번째 도시를 선택하십시오.

나는 그것이 도움이되기를 바랍니다 :)


31

그것은 나를 위해 일했다. 단순히 사용

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

"Type"을 원하는 유형으로 바꾸십시오 (예 : int).


1
MSDN 페이지에서보고 한 것처럼 System.Collections.Generic이 아닌 Linq에 고유 한 이름이 있습니다.
Almo

5
이 답변 (2012)은이 페이지에서 2008 년에 나온 다른 두 답변과 같은 것 같습니다.
Jon Schneider

23

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()에서 항목을 제거하는 것보다 컬렉션을 필터링하는 것이 훨씬 빠릅니다 ( 이 샘플과 마찬가지로).


그러나이 접근법의 문제점은 해시 세트와 달리 O (N ^ 2) -ish라는 것입니다. 그러나 적어도 그것이 무엇을하고 있는지 분명합니다.
Tamas Czinege

1
@ DrJokepu-실제로 HashSet생성자가 중복 제거되었음을 알지 못하여 대부분의 환경에서 더 좋습니다. 그러나 이것은 정렬 순서를 유지하지만 HashSet그렇지 않습니다.
Keith

1
HashSet <T>가 3.5에 소개되었습니다
thorn̈

1
정말 가시? 추적하기가 어렵습니다. 이 경우 당신은 단지를 사용할 수 Dictionary<T, object>대신 교체 .Contains.ContainsKey.Add(item)함께.Add(item, null)
키스

내 테스트에 따라 @Keith 는 HashSet주문을 유지하지만 Distinct()그렇지 않습니다.
Dennis T-복귀 모니카-15

13

확장 방법은 괜찮은 방법 일 수 있습니다 ...

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

그리고 다음과 같이 호출하십시오.

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

11

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으로 바꾸십시오.


5
C #에서는 다음과 같습니다. List <T> noDupes = new List <T> (new HashSet <T> (list)); list.Clear (); list.AddRange (noDupes);
smohamed

C #에서는 다음과 같이 쉽습니다. var noDupes = new HashSet<T>(list); list.Clear(); list.AddRange(noDupes);:)
nawfal

10

이것은 (요소를 복제하지 않은 요소) 구별되고 다시 목록으로 변환합니다.

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

9

Linq의 Union 방법을 사용하십시오 .

참고 :이 솔루션에는 Linq에 대한 지식이 필요합니다.

암호

클래스 파일 맨 위에 다음을 추가하여 시작하십시오.

using System.Linq;

이제 다음을 사용하여이라는 객체에서 중복을 제거 할 수 있습니다 obj1.

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

참고 : obj1개체 이름을 바꾸 십시오.

작동 원리

  1. Union 명령은 두 소스 개체의 각 항목 중 하나를 나열합니다. obj1은 모두 소스 객체이므로 obj1을 각 항목 중 하나로 줄입니다.

  2. ToList()새로운 목록을 반환합니다. Linq 명령과 같은 Linq 명령 Union은 원래 List를 수정하거나 새 List를 반환하는 대신 IEnumerable 결과로 결과를 반환 하기 때문에 필요 합니다.


7

(Linq없이) 도우미 방법으로 :

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

나는 이미 구별되고 있다고 생각합니다. 그 외에도 (메소드 이름을 바꾸면) 작동합니다.
Andreas Reiff

6

당신이 순서에 대한 상관 없어 경우에 당신은 단지에 항목을 밀어 수 있습니다 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)정렬 방법은 열등한 것을 (먼저 눈에 있었다으로) 그렇게 나에게 분명하지 않도록 공간 (내 일시적 다운 투표에 대한 사과 ...)


6

인접한 복제본을 제자리에서 제거하는 확장 방법이 있습니다. 먼저 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);
}

5

Nuget을 통해 MoreLINQ 패키지를 설치하면 속성별로 객체 목록을 쉽게 구별 할 수 있습니다.

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

3

중복이 목록에 추가되지 않도록하는 것이 더 쉬울 수 있습니다.

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

1
나는 현재 이와 같이하고 있지만 더 많은 항목을 가질수록 중복 검사가 더 오래 걸립니다.
Robert Strauch 2016 년

나는 여기에 같은 문제가 있습니다. List<T>.Contains매번 이 방법을 사용하고 있지만 1,000,000 개 이상의 항목이 있습니다. 이 프로세스는 내 응용 프로그램을 느리게합니다. List<T>.Distinct().ToList<T>()대신 첫 번째를 사용하고 있습니다.
RPDeshaies

이 방법은 매우 느리다
darkgaze

3

Union을 사용할 수 있습니다

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

7
그것이 효과가 좋은지 설명 하면이 답변이 더 나아질 것입니다.
Igor B

2

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

2

해결 방법에는 여러 가지가 있습니다. 목록의 중복 문제는 다음 중 하나입니다.

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

건배 라비 가네 산


2

다음은 읽기 어려운 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
                }
            }
        }
    }

이 방법으로 복제 된 항목을 더 많이 제어 할 수 있습니다. 업데이트 할 데이터베이스가있는 경우 훨씬 더 좋습니다. innerIndex의 경우, 매번 처음부터 시작하는 outerIndex + 1부터 시작하지 않는 이유는 무엇입니까?
Nolmë Informatique

2

David J.의 답변은 좋은 방법이며 추가 객체, 정렬 등이 필요하지 않습니다. 그러나 다음과 같이 개선 될 수 있습니다.

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

따라서 외부 루프는 전체 목록에서 맨 아래로 이동하지만 내부 루프는 "외부 루프 위치에 도달 할 때까지"맨 아래로 이동합니다.

외부 루프는 전체 목록이 처리되도록하고 내부 루프는 실제 중복을 찾고 외부 루프가 아직 처리하지 않은 부분에서만 발생할 수 있습니다.

또는 내부 루프에 대해 상향식을 수행하지 않으려면 outerIndex + 1에서 내부 루프를 시작할 수 있습니다.


2

모든 답변은 목록을 복사하거나 새 목록을 만들거나 느린 기능을 사용하거나 고통스럽게 느립니다.

내가 알기로는 이것이 내가 알고 있는 가장 빠르고 저렴한 방법입니다 (실시간 물리 최적화를 전문으로하는 숙련 된 프로그래머가 지원합니다).

// 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 기능을 사용하지 않기 때문에이 작업의 속도를 정확히 알지 못하지만 가장 빠른 방법이라고 생각합니다.


2

견인 클래스가 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속성 별로 중복 항목을 제거 할 수 있습니다 .


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

간단한 직관적 인 구현 :

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

이 방법도 느립니다. 새 목록을 만듭니다.
darkgaze
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.