순서에 관계없이 문자열 목록의 해시 가져 오기
GetHashCodeOfList()
순서에 관계없이 문자열 목록의 해시 코드를 반환 하는 함수를 작성하고 싶습니다 . 동일한 문자열을 가진 2 개의 목록이 주어지면 동일한 해시 코드를 반환해야합니다.
ArrayList list1 = new ArrayList()
list1.Add("String1");
list1.Add("String2");
list1.Add("String3");
ArrayList list2 = new ArrayList()
list2.Add("String3");
list2.Add("String2");
list2.Add("String1");
GetHashCodeOfList(list1) = GetHashCodeOfList(list2) //this should be equal.
몇 가지 생각이있었습니다.
먼저 목록을 정렬 한 다음 정렬 된 목록을 하나의 긴 문자열로 결합한 다음
GetHashCode()
. 그러나 정렬은 느린 작업입니다.string.GetHashCode()
목록에서 를 호출하여 각 개별 문자열의 해시를 얻은 다음 모든 해시를 곱하고 Mod를 호출 할 수UInt32.MaxValue
있습니다. 예 :"String1".GetHashCode() * "String2".GetHashCode * … MOD UInt32.MaxValue
. 그러나 이로 인해 숫자 오버플로가 발생합니다.
누구 생각이 있습니까?
도움을 주셔서 미리 감사드립니다.
여기에는 두 가지 주요 범주 아래에 다양한 접근 방식이 있으며, 각 범주에는 일반적으로 효율성과 성능 측면에서 고유 한 장점과 단점이 있습니다. 응용 프로그램에 대해 가장 간단한 알고리즘을 선택하고 상황에 따라 필요한 경우에만 더 복잡한 변형을 사용하는 것이 가장 좋습니다.
이 예제 EqualityComparer<T>.Default
는 null 요소를 깔끔하게 처리하므로 사용 합니다. 원하는 경우 null에 대해 0보다 더 잘 할 수 있습니다. T가 struct로 제한된 경우에도 필요하지 않습니다. EqualityComparer<T>.Default
원하는 경우 함수 에서 조회 를 끌어 올 수 있습니다 .
교환 작업
교환 가능한 개별 항목의 해시 코드에 대한 작업을 사용하면 순서에 관계없이 동일한 최종 결과를 얻을 수 있습니다.
숫자에 대한 몇 가지 분명한 옵션이 있습니다.
XOR
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
foreach (T element in source)
{
hash = hash ^ EqualityComparer<T>.Default.GetHashCode(element);
}
return hash;
}
한 가지 단점은 { "x", "x"}의 해시가 { "y", "y"}의 해시와 동일하다는 것입니다. 하지만 이것이 귀하의 상황에 문제가되지 않는다면 아마도 가장 간단한 해결책 일 것입니다.
부가
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
foreach (T element in source)
{
hash = unchecked (hash +
EqualityComparer<T>.Default.GetHashCode(element));
}
return hash;
}
여기서 오버플로는 괜찮으므로 명시적인 unchecked
컨텍스트입니다.
여전히 몇 가지 불쾌한 경우 (예 : {1, -1} 및 {2, -2})가 있지만 특히 문자열에서는 괜찮을 가능성이 더 높습니다. 이러한 정수를 포함 할 수있는 목록의 경우에는 항상 다음을 구현할 수 있습니다. 사용자 지정 해싱 함수 (아마도 특정 값의 반복 색인을 매개 변수로 사용하고 이에 따라 고유 한 해시 코드를 반환하는 함수).
다음은 상당히 효율적인 방식으로 앞서 언급 한 문제를 해결하는 알고리즘의 예입니다. 또한 생성 된 해시 코드의 분포를 크게 늘릴 수있는 이점도 있습니다 (일부 설명은 마지막에 링크 된 문서 참조). 이 알고리즘이 "더 나은"해시 코드를 생성하는 정확한 방법에 대한 수학적 / 통계적 분석은 상당히 발전 할 수 있지만 광범위한 입력 값에 대해 테스트하고 결과를 플로팅하면 충분히 검증되어야합니다.
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
int curHash;
int bitOffset = 0;
// Stores number of occurences so far of each value.
var valueCounts = new Dictionary<T, int>();
foreach (T element in source)
{
curHash = EqualityComparer<T>.Default.GetHashCode(element);
if (valueCounts.TryGetValue(element, out bitOffset))
valueCounts[element] = bitOffset + 1;
else
valueCounts.Add(element, bitOffset);
// The current hash code is shifted (with wrapping) one bit
// further left on each successive recurrence of a certain
// value to widen the distribution.
// 37 is an arbitrary low prime number that helps the
// algorithm to smooth out the distribution.
hash = unchecked(hash + ((curHash << bitOffset) |
(curHash >> (32 - bitOffset))) * 37);
}
return hash;
}
곱셈
덧셈에 비해 이점이 거의 없습니다. 작은 숫자와 양수와 음수의 혼합은 해시 비트의 더 나은 분포로 이어질 수 있습니다. 이 "1"을 상쇄하기위한 음수는 아무것도 기여하지 않는 쓸모없는 항목이되고 0 요소는 0이됩니다. 이 중대한 결함을 일으키지 않도록 특별한 경우 0을 사용할 수 있습니다.
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 17;
foreach (T element in source)
{
int h = EqualityComparer<T>.Default.GetHashCode(element);
if (h != 0)
hash = unchecked (hash * h);
}
return hash;
}
먼저 주문
다른 핵심 접근 방식은 먼저 순서를 적용한 다음 원하는 해시 조합 함수를 사용하는 것입니다. 순서 자체는 일관성이있는 한 중요하지 않습니다.
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
int hash = 0;
foreach (T element in source.OrderBy(x => x, Comparer<T>.Default))
{
// f is any function/code you like returning int
hash = f(hash, element);
}
return hash;
}
이것은에서 가능한 결합 작업 f
이 훨씬 더 나은 해싱 속성 (예 : 비트 분산)을 가질 수 있다는 점에서 상당한 이점 이 있지만 훨씬 더 높은 비용이 발생합니다. 정렬은 O(n log n)
컬렉션의 필수 복사본은 원본을 수정하지 않으려는 욕구를 감안할 때 피할 수없는 메모리 할당입니다. GetHashCode
구현은 일반적으로 할당을 완전히 피해야합니다. 의 가능한 구현 중 하나 f
는 덧셈 섹션의 마지막 예에서 제공된 것과 유사합니다 (예 : 상수 비트 시프트 후 소수 곱하기-추가 비용없이 각 반복에서 연속 소수를 사용할 수도 있음). 한 번만 생성하면됩니다.)
즉, 해시를 계산하고 캐시 할 수있는 경우를 처리 GetHashCode
하고이 접근 방식에 대한 많은 호출에 대한 비용을 상각 할 수있는 경우 우수한 동작을 생성 할 수 있습니다. 또한 후자의 접근 방식은 GetHashCode
요소의 유형을 알고있는 경우 요소에서 를 사용할 필요가 없고 대신에 더 나은 해시 분포를 생성하기 위해 바이트 당 작업 을 사용할 필요가 없기 때문에 훨씬 더 유연 합니다. 이러한 접근 방식은 성능이 심각한 병목 현상으로 확인 된 경우에만 사용됩니다.
마지막으로, 해시 코드의 주제와 일반적인 효과에 대해 합리적으로 포괄적이고 상당히 비 수학적 개요를 원한다면 이 블로그 게시물 , 특히 간단한 해싱 알고리즘 구현 (pt II) 게시물을 읽어 볼 가치가 있습니다 .
문자열 목록 정렬의 대안은 문자열의 해시 코드를 가져온 다음 해시 코드를 정렬하는 것입니다. (int를 비교하는 것은 문자열을 비교하는 것보다 비용이 적게 듭니다.) 그런 다음 알고리즘을 사용하여 더 나은 배포를 제공하는 해시 코드를 병합 할 수 있습니다.
예:
GetHashCodeOfList<T>(IEnumerable<T> list) {
List<int> codes = new List<int>();
foreach (T item in list) {
codes.Add(item.GetHashCode());
}
codes.Sort();
int hash = 0;
foreach (int code in codes) {
unchecked {
hash *= 251; // multiply by a prime number
hash += code; // add next hash code
}
}
return hash;
}
Dim list1 As ArrayList = New ArrayList()
list1.Add("0")
list1.Add("String1")
list1.Add("String2")
list1.Add("String3")
list1.Add("abcdefghijklmnopqrstuvwxyz")
Dim list2 As ArrayList = New ArrayList()
list2.Add("0")
list2.Add("String3")
list2.Add("abcdefghijklmnopqrstuvwxyz")
list2.Add("String2")
list2.Add("String1")
If GetHashCodeOfList(list1) = GetHashCodeOfList(list2) Then
Stop
Else
Stop
End If
For x As Integer = list1.Count - 1 To 0 Step -1
list1.RemoveAt(list1.Count - 1)
list2.RemoveAt(list2.Count - 1)
Debug.WriteLine(GetHashCodeOfList(list1).ToString)
Debug.WriteLine(GetHashCodeOfList(list2).ToString)
If list1.Count = 2 Then Stop
Next
Private Function GetHashCodeOfList(ByVal aList As ArrayList) As UInt32
Const mask As UInt16 = 32767, hashPrime As Integer = Integer.MaxValue
Dim retval As UInt32
Dim ch() As Char = New Char() {}
For idx As Integer = 0 To aList.Count - 1
ch = DirectCast(aList(idx), String).ToCharArray
For idCH As Integer = 0 To ch.Length - 1
retval = (retval And mask) + (Convert.ToUInt16(ch(idCH)) And mask)
Next
Next
If retval > 0 Then retval = Convert.ToUInt32(hashPrime \ retval) 'Else ????
Return retval
End Function
코드가 훨씬 적지 만 성능이 다른 답변만큼 좋지 않을 수 있습니다.
public static int GetOrderIndependentHashCode<T>(this IEnumerable<T> source)
=> source == null ? 0 : HashSet<T>.CreateSetComparer().GetHashCode(new HashSet<T>(source));
Here is a hybrid approach. It combines the three commutative operations (XOR, addition and multiplication), applying each one in different ranges of the 32bit number. The bit-range of each operation is adjustable.
public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
var comparer = EqualityComparer<T>.Default;
const int XOR_BITS = 10;
const int ADD_BITS = 11;
const int MUL_BITS = 11;
Debug.Assert(XOR_BITS + ADD_BITS + MUL_BITS == 32);
int xor_total = 0;
int add_total = 0;
int mul_total = 17;
unchecked
{
foreach (T element in source)
{
var hashcode = comparer.GetHashCode(element);
int xor_part = hashcode >> (32 - XOR_BITS);
int add_part = hashcode << XOR_BITS >> (32 - ADD_BITS);
int mul_part = hashcode << (32 - MUL_BITS) >> (32 - MUL_BITS);
xor_total = xor_total ^ xor_part;
add_total = add_total + add_part;
if (mul_part != 0) mul_total = mul_total * mul_part;
}
xor_total = xor_total % (1 << XOR_BITS); // Compact
add_total = add_total % (1 << ADD_BITS); // Compact
mul_total = mul_total - 17; // Subtract initial value
mul_total = mul_total % (1 << MUL_BITS); // Compact
int result = (xor_total << (32 - XOR_BITS)) + (add_total << XOR_BITS) + mul_total;
return result;
}
}
The performance is almost identical with the simple XOR method, because the call to GetHashCode
of each element dominates the CPU demand.
참고URL : https://stackoverflow.com/questions/670063/getting-hash-of-a-list-of-strings-regardless-of-order
'code' 카테고리의 다른 글
Python 2.7 용 메모 라이브러리 (0) | 2020.11.28 |
---|---|
./ 도트 슬래시로 시작하는 경로가있는 현재 디렉토리의 GitLab 마크 다운에서 이미지를 어떻게 참조 할 수 있습니까? (0) | 2020.11.28 |
Maven-jar에 임의의 클래스 경로 항목을 어떻게 추가 할 수 있습니까? (0) | 2020.11.28 |
CodeIgniter에서 pconnect 옵션의 장점 / 단점 (0) | 2020.11.28 |
Java / ImageIO가 전체 파일을 읽지 않고 이미지 크기를 얻습니까? (0) | 2020.11.27 |