언제 HashSet <T> 유형을 사용해야합니까?


134

HashSet<T>유형을 탐색하고 있지만 컬렉션에서 어디에 있는지 이해하지 못합니다.

그것을 대체하기 위해 그것을 사용할 수 있습니까 List<T>? 나는 a의 성능 HashSet<T>이 더 뛰어나다 고 생각 하지만 그 요소에 대한 개별 액세스를 볼 수 없었습니다.

열거 전용입니까?

답변:


228

중요한 것은 HashSet<T>이름에 바로 있습니다 : 그것은 집합 입니다. 단일 세트로 수행 할 수있는 유일한 작업은 멤버가 무엇인지 설정하고 항목이 멤버인지 여부를 확인하는 것입니다.

단일 요소 (예 :)를 검색 할 수 있는지 묻는 set[45]것은 세트의 개념을 오해하고 있습니다. 세트의 45 번째 요소와 같은 것은 없습니다. 세트의 품목에는 주문이 없습니다. {1, 2, 3} 및 {2, 3, 1} 세트는 멤버십이 동일하므로 멤버십이 중요하므로 모든 점에서 동일합니다.

세트를 반복하는 HashSet<T>것은 세트의 아이템에 순서를 부과하기 때문에 다소 위험합니다 . 그 순서는 실제로 집합의 속성이 아닙니다. 당신은 그것에 의존해서는 안됩니다. 컬렉션의 항목 순서가 중요한 경우 해당 컬렉션은 설정되지 않습니다.

세트는 실제로 제한되며 고유 한 멤버로 구성됩니다. 반면에, 그들은 정말 빠릅니다.


1
프레임 워크가 SortedSet데이터 구조를 제공한다는 사실 은 주문이 세트의 속성이 아닌 것에 대해 말하는 것과 모순되거나 개발 팀의 오해를 지적합니다.
Veverke

10
에있는 항목의 순서가 HashSet정의되어 있지 않다고 말하는 것이 더 정확하다고 생각 하므로 반복자의 순서에 의존하지 마십시오. 세트의 항목에 대해 무언가를 수행하고 있기 때문에 세트를 반복하는 경우 주문과 관련된 항목에 의존 하지 않는 한 위험 하지 않습니다 . A는 의 모든 속성이 플러스 그러나, 순서 에서 파생하지 않는다 ; 다시 말해, SortedSet은 정렬 된 고유 한 개체 모음입니다 . SortedSetHashSet SortedSetHashSet
키트

110

다음은 내가 사용하는 실제 예입니다 HashSet<string>.

UnrealScript 파일 용 구문 강조 표시의 일부는 Doxygen 스타일 설명강조 표시 하는 새로운 기능입니다 . 회색 (유효) 또는 빨간색 (유효)으로 표시할지 결정하기 위해 @또는 \명령이 유효한지 알 수 있어야합니다 . 나는이 HashSet<string>내가 공격 할 때마다 그래서, 모든 유효한 명령을 @xxx렉서의 토큰을, 내가 사용하는 validCommands.Contains(tokenText)내 O (1) 유효 기간 확인한다. 유효한 명령 집합 에 명령이 존재 하는 것 외에는 아무것도 신경 쓰지 않습니다 . 내가 직면 한 대안을 살펴 보겠습니다.

  • Dictionary<string, ?>: 값에 어떤 유형을 사용합니까? 방금 사용하기 때문에 값이 의미가 없습니다 ContainsKey. 참고 : .NET 3.0 이전에는 O (1) 조회를위한 유일한 선택 HashSet<T>이었습니다. 3.0에 추가 ISet<T>되고 4.0 에 구현 되도록 확장되었습니다 .
  • List<string>: 목록을 정렬 상태로 유지하면 BinarySearchO (log n) 인 (위에서 언급 한 사실을 보지 못함)을 사용할 수 있습니다 . 그러나 유효한 명령 목록은 절대 바뀌지 않는 고정 목록이므로 간단하지 않습니다.
  • string[]: 다시, Array.BinarySearchO (log n) 성능을 제공합니다. 목록이 짧으면 이것이 가장 성능이 좋은 옵션 일 수 있습니다. 항상보다 적은 공간 오버 헤드가 HashSet, Dictionary또는 List. 로도 BinarySearch큰 세트의 경우 더 빠르지는 않지만 작은 세트의 경우 실험 해 볼 가치가 있습니다. 광산에는 수백 개의 항목이 있으므로 이것을 전달했습니다.

24

A는 HashSet<T>구현 ICollection<T>인터페이스 :

public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
    // Methods
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);

    // Properties
   int Count { get; }
   bool IsReadOnly { get; }
}

List<T>구현 IList<T>확장,ICollection<T>

public interface IList<T> : ICollection<T>
{
    // Methods
    int IndexOf(T item);
    void Insert(int index, T item);
    void RemoveAt(int index);

    // Properties
    T this[int index] { get; set; }
}

HashSet은 내부적으로 해시 테이블을 통해 구현 된 의미를 설정했습니다.

집합은 중복 요소가없고 특정 순서가 아닌 컬렉션입니다.

인덱스 / 포지션 /리스트 동작을 잃으면 HashSet이 얻는 이점

HashSet에서 항목을 추가하고 검색하는 것은 항상 인덱서를 통하지 않고 객체 자체에 의해 이루어지며 O (1) 연산에 가깝습니다 (목록은 O (1) 추가, O (1)은 색인으로 검색, O (n) 찾기 /없애다).

Dictionary<TKey,TValue>키를 값으로 추가 / 제거하고 사전 값 자체를 무시하는 것만으로 HashSet의 동작을 비교할 수 있습니다 . 딕셔너리의 키가 중복 된 값을 가지지 않기를 기대할 수 있습니다. 이것이 바로 "설정"부분입니다.


14

성능은 목록보다 HashSet을 선택하는 나쁜 이유입니다. 대신 의도를 더 잘 포착하는 것은 무엇입니까? 순서가 중요한 경우 Set (또는 HashSet)이 종료됩니다. 마찬가지로 복제가 허용되는 경우. 그러나 주문에 신경 쓰지 않는 상황이 많으며 복제본이 없을 것입니다. 그러면 세트를 원할 때입니다.


21
Performance would be a bad reason to choose HashSet over List: 난 당신의 의견에 동의하지 않습니다. 그것은 두 개의 목록 대신에 Dictionray를 선택하는 것이 성능에 도움이되지 않는다는 것입니다. 한 번 봐 가지고 다음 문서를
오스카 Mederos에게

11
@ 오스카 : 나는 세트가 더 빠르지 않다고 말하지 않았다-나는 그것이 선택을위한 나쁜 기초가 될 것이라고 말했다. 당신이 주문한 컬렉션을 표현하려고한다면, 세트는 작동하지 않을 것이고 컬렉션에 삽입하려고 시도하는 것은 실수 일 것입니다. 원하는 컬렉션에 주문이 없으면 세트가 완벽하고 빠릅니다. 그러나 중요한 것은 첫 번째 질문입니다. 무엇을 표현하려고합니까?
Carl Manaster

2
하지만 생각 해봐 당신은 주어진 문자열 만 문자열의 일부 집합의 구성원인지 확인을 유지하려면, 기술적, string[].Contains그리고 HashSet<string>.Contains의도 동일하게 표현; HashSet을 선택하는 이유는 훨씬 빠르게 실행되기 때문입니다.
Casey

12

HashSet은 해싱으로 구현 된 집합 입니다. 집합은 중복 요소가없는 값의 모음입니다. 집합의 값은 일반적으로 순서가 없습니다. 따라서, 세트를 사용하여 목록을 대체 할 수 없습니다 (처음에 세트를 사용해야하지 않는 한).

어떤 세트가 좋은지 궁금해하는 경우 : 분명히 복제본을 없애고 싶은 곳. 약간의 예를 들어, 소프트웨어 프로젝트의 10.000 개정 목록이 있고 그 프로젝트에 얼마나 많은 사람들이 기여했는지 알고 싶다고 가정 해 봅시다. a를 사용하여 Set<string>개정 목록을 반복하고 각 개정의 작성자를 세트에 추가 할 수 있습니다 . 반복을 마치면 세트의 크기가 원하는 답입니다.


그러나 Set은 단일 요소 검색을 허용하지 않습니까? 세트처럼 [45]?
Joan Venge

2
이를 위해 멤버 집합을 반복합니다. 다른 일반적인 작업은 집합에 요소가 포함되어 있는지 확인하거나 집합의 크기를 가져 오는 것입니다.

11

HashSet은 IEnumerable 컬렉션에서 중복 요소를 제거하는 데 사용됩니다. 예를 들어

List<string> duplicatedEnumrableStrings = new List<string> {"abc", "ghjr", "abc", "abc", "yre", "obm", "ghir", "qwrt", "abc", "vyeu"};
HashSet<string> uniqueStrings = new HashSet(duplicatedEnumrableStrings);

해당 코드가 실행 된 후 uniqueStrings는 { "abc", "ghjr", "yre", "obm", "qwrt", "vyeu"}를 보유합니다.


6

아마도 해시 셋에 대한 가장 일반적인 용도는 포함에 대한 검사가 O ( n) (그리고 O (log n) 인 정렬 세트). 따라서 항목을 일부 목록에 포함했는지 여부를 많이 확인하면 성능 향상이 될 수 있습니다. 그것들을 반복하는 것만으로도 큰 차이는 없습니다 (목록과 해시 세트가 항목을 추가 할 때 약간 더 많은 오버 헤드가있는 것과 같이 전체 세트를 반복하는 것은 O (n)입니다).

그리고 아니요, 세트는 순서가 없기 때문에 어쨌든 의미가없는 세트를 색인 할 수 없습니다. 일부 항목을 추가하면 세트는 첫 번째 항목과 두 번째 등을 기억하지 않습니다.


반복적으로 반복하면 HashSet 메서드는 List에 비해 약간의 메모리 사용량을 추가합니다.
SamuelWarren

5

HashSet<T>.NET 프레임 워크의 데이터 구조는 수학적 집합 을 객체로 표현할 수 있습니다. 이 경우 해시 코드 ( GetHashCode각 항목 의 결과)를 사용하여 설정된 요소의 동등성을 비교합니다.

세트는 그 안에 포함 된 동일한 요소를 한 번만 허용한다는 점에서 목록과 다릅니다. 두 번째 동일한 요소를 추가하려고하면 HashSet<T>반환 false됩니다. 실제로 O(1)내부 데이터 구조는 단순히 해시 테이블이기 때문에 요소 검색이 매우 빠릅니다 ( 시간).

어떤 것을 사용해야할지 궁금하다면, List<T>where HashSet<T>is appropiate 를 사용하는 것이 가장 큰 실수는 아니지만 컬렉션에 바람직하지 않은 중복 항목이있는 경우 문제가 발생할 수 있습니다. 또한 조회 (항목 검색)는 시간이 O(1)아닌 이상적으로 (완벽한 버킷 팅을 위해 ) 훨씬 더 효율적 O(n)입니다. 이는 많은 시나리오에서 매우 중요합니다.


1
기존 항목을 세트에 추가해도 예외는 발생하지 않습니다. 추가는 단순히 거짓을 반환합니다. 또한 해시 함수가 완벽하지 않으면 기술적으로 해시 조회는 O (1)이 아닌 O (n)입니다. 물론 해싱 함수가 실제로 나쁘지 않은 한 O (1)라고 가정하면 실제로 벗어날 수 있습니다.
sepp2k

1
@ sepp2k : 예, 부울을 반환합니다 ... 요점은 알려줍니다. 버킷 팅이 끔찍한 경우 해시 조회는 최악의 경우 O (n)입니다. 일반적으로 O (1)에 훨씬 가깝습니다.
Noldorin

4

List<T>정렬 된 정보 세트를 저장하는 데 사용됩니다. 목록 요소의 상대 순서를 알고 있으면 일정한 시간에 액세스 할 수 있습니다. 그러나 요소가 목록에있는 위치를 결정하거나 요소가 목록에 있는지 확인하기 위해 조회 시간은 선형입니다. 반면에 HashedSet<T>저장된 데이터의 순서를 보장하지 않으며 결과적으로 해당 요소에 대한 지속적인 액세스 시간을 제공합니다.

이름에서 알 수 있듯이 set semanticsHashedSet<T> 를 구현하는 데이터 구조입니다 . 데이터 구조는 전통적인 List 구현으로는 효율적으로 수행 할 수없는 집합 연산 (예 : Union, Difference, Intersect)을 구현하도록 최적화되어 있습니다.

따라서 사용할 데이터 유형을 선택하는 것은 실제로 응용 프로그램에서 수행하려는 작업에 따라 다릅니다. 컬렉션에서 요소를 정렬하는 방법에 신경 쓰지 않고 존재를 확인하거나 존재를 확인하려는 경우을 사용하십시오 HashSet<T>. 그렇지 않으면 List<T>또는 다른 적절한 데이터 구조를 사용하십시오.


2
또 다른 경고 : 집합은 일반적으로 한 번의 요소 만 허용합니다.
Steve Guidi

1

요컨대-사전 (또는 S가 T의 속성 인 사전)을 사용하려고 할 때마다 HashSet (또는 S와 동등한 T에서 IEquatable을 구현하는 HashSet +)을 고려해야합니다.


5
키에 신경 쓰지 않는 한 사전을 사용해야합니다.
Hardwareguy

1

기본 의도 된 시나리오에서는 HashSet<T>LINQ가 제공하는 것보다 두 개의 콜렉션에 대해보다 구체적인 세트 조작을 원할 때 사용해야합니다. LINQ 방법처럼 Distinct, Union, IntersectExcept대부분의 상황에서 충분하지만, 때로는 좀 더 세밀한 작업을 필요로 할 수 있으며, HashSet<T>제공 :

  • UnionWith
  • IntersectWith
  • ExceptWith
  • SymmetricExceptWith
  • Overlaps
  • IsSubsetOf
  • IsProperSubsetOf
  • IsSupersetOf
  • IsProperSubsetOf
  • SetEquals

LINQ와 HashSet<T>"중복"메소드의 또 다른 차이점은 LINQ 는 항상 새로운을 리턴 IEnumerable<T>하고 HashSet<T>메소드는 소스 콜렉션을 수정한다는 것입니다.

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