C #에서 사전 조회 테이블을 만들려고합니다. 3 튜플의 값을 하나의 문자열로 해결해야합니다. 배열을 키로 사용해 보았지만 작동하지 않았고 다른 작업을해야할지 모르겠습니다. 이 시점에서 나는 사전 사전을 만드는 것을 고려하고 있지만, 자바 스크립트로하는 방법이지만보기에는보기에 그리 예쁘지 않을 것입니다.
답변
.NET 4.0을 사용하는 경우 Tuple을 사용하십시오.
lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
그렇지 않은 경우 a를 정의 Tuple
하고 키로 사용할 수 있습니다 . Tuple은 GetHashCode
, Equals
및 IEquatable
다음 을 재정의해야합니다 .
struct Tuple<T, U, W> : IEquatable<Tuple<T,U,W>>
{
readonly T first;
readonly U second;
readonly W third;
public Tuple(T first, U second, W third)
{
this.first = first;
this.second = second;
this.third = third;
}
public T First { get { return first; } }
public U Second { get { return second; } }
public W Third { get { return third; } }
public override int GetHashCode()
{
return first.GetHashCode() ^ second.GetHashCode() ^ third.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Equals((Tuple<T, U, W>)obj);
}
public bool Equals(Tuple<T, U, W> other)
{
return other.first.Equals(first) && other.second.Equals(second) && other.third.Equals(third);
}
}
답변
튜플과 중첩 된 사전 기반 접근 방식 사이에서는 거의 항상 튜플 기반을 사용하는 것이 좋습니다.
유지 관리의 관점에서 ,
-
다음과 같은 기능을 구현하는 것이 훨씬 쉽습니다.
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
…보다
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
수신자 측에서. 두 번째 경우 각 추가, 조회, 제거 등은 둘 이상의 사전에 대한 조치가 필요합니다.
-
또한 복합 키에 앞으로 하나 이상의 (또는 더 적은) 필드가 필요한 경우 중첩 된 사전과 후속 검사를 더 추가해야하므로 두 번째 경우 (중첩 된 사전)에서 코드를 상당히 많이 변경해야합니다.
성능 관점 에서 도달 할 수있는 최상의 결론은 직접 측정하는 것입니다. 그러나 미리 고려할 수있는 몇 가지 이론적 한계가 있습니다.
-
중첩 된 딕셔너리의 경우 모든 키 (외부 및 내부)에 대한 추가 딕셔너리가 있으면 약간의 메모리 오버 헤드가 발생합니다 (튜플을 만드는 것보다 더 많음).
-
중첩 된 사전의 경우 추가, 업데이트, 조회, 제거 등과 같은 모든 기본 작업을 두 개의 사전에서 수행해야합니다. 이제 중첩 된 사전 접근 방식이 더 빠를 수있는 경우가 있습니다. 즉, 조회되는 데이터가 없을 때 중간 사전이 전체 해시 코드 계산 및 비교를 우회 할 수 있지만 다시 확인해야합니다. 데이터가있는 경우 조회를 두 번 (또는 중첩에 따라 세 번) 수행해야하므로 속도가 느려집니다.
-
튜플 접근 방식과 관련하여 .NET 튜플은 세트의 키로 사용되어야 할 때 가장 성능이 좋지 않습니다. 그
Equals
및GetHashCode
구현으로 인해 값 유형에 대한 박싱이 발생하기 때문 입니다.
튜플 기반 사전을 사용하지만 더 많은 성능을 원한다면 더 나은 구현으로 내 튜플을 사용합니다.
참고로 사전을 멋지게 만들 수있는 화장품은 거의 없습니다.
-
인덱서 스타일 호출은 훨씬 깔끔하고 직관적 일 수 있습니다. 예를 들어,
string foo = dict[a, b, c]; //lookup dict[a, b, c] = ""; //update/insertion
따라서 삽입과 조회를 내부적으로 처리하는 사전 클래스에 필요한 인덱서를 노출하십시오.
-
또한 적절한
IEnumerable
인터페이스를 구현하고Add(TypeA, TypeB, TypeC, string)
다음과 같이 컬렉션 이니셜 라이저 구문을 제공하는 메서드를 제공합니다.new MultiKeyDictionary<TypeA, TypeB, TypeC, string> { { a, b, c, null }, ... };
답변
C # 7을 사용하는 경우 값 튜플을 복합 키로 사용하는 것을 고려해야합니다. 값 튜플은 일반적으로 기존 참조 튜플 ( Tuple<T1, …>
) 보다 더 나은 성능을 제공합니다. 값 튜플은 참조 유형이 아닌 값 유형 (구조체)이므로 메모리 할당 및 가비지 수집 비용을 피합니다. 또한 간결하고 직관적 인 구문을 제공하여 원하는 경우 필드 이름을 지정할 수 있습니다. 또한 IEquatable<T>
사전에 필요한 인터페이스를 구현합니다 .
var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();
답변
좋고, 깨끗하고, 빠르고, 쉽고, 읽기 쉬운 방법은 다음과 같습니다.
- 현재 유형에 대해 동등 멤버 (Equals () 및 GetHashCode ()) 를 생성합니다. ReSharper 와 같은 도구 는 메서드를 생성 할뿐만 아니라 동등성 검사 및 / 또는 해시 코드 계산에 필요한 코드도 생성합니다. 생성 된 코드는 Tuple 실현보다 최적입니다.
- 튜플에서 파생 된 간단한 키 클래스를 만드십시오 .
다음과 비슷한 것을 추가하십시오.
public sealed class myKey : Tuple<TypeA, TypeB, TypeC>
{
public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { }
public TypeA DataA => Item1;
public TypeB DataB => Item2;
public TypeC DataC => Item3;
}
따라서 사전과 함께 사용할 수 있습니다.
var myDictinaryData = new Dictionary<myKey, string>()
{
{new myKey(1, 2, 3), "data123"},
{new myKey(4, 5, 6), "data456"},
{new myKey(7, 8, 9), "data789"}
};
- 계약서에서도 사용할 수 있습니다.
- linq에서 조인 또는 그룹화를위한 키로
- 이렇게하면 Item1, Item2, Item3의 순서를 잘못 입력하지 않습니다.
- 무언가를 얻기 위해 어디로 가야하는지 이해하기 위해 코드를 기억하거나 살펴볼 필요가 없습니다.
- IStructuralEquatable, IStructuralComparable, IComparable, ITuple을 모두 재정의 할 필요가 없습니다.
답변
어떤 이유로 든 자신 만의 Tuple 클래스를 만들거나 .NET 4.0에 내장 된 클래스를 사용하지 않으려는 경우 가능한 다른 방법이 하나 있습니다. 세 개의 키 값을 하나의 값으로 결합 할 수 있습니다.
예를 들어, 세 값이 64 비트를 넘지 않는 정수 유형 인 경우이를 결합하여 ulong
.
최악의 경우에는 문자열의 세 구성 요소가 키의 구성 요소 내에서 발생하지 않는 일부 문자 또는 시퀀스로 구분되어 있는지 확인하는 한 항상 문자열을 사용할 수 있습니다.
string.Format("{0}#{1}#{2}", key1, key2, key3)
이 접근 방식에는 분명히 약간의 컴포지션 오버 헤드가 있지만,이를 위해 사용하는 것에 따라 신경 쓰지 않을 정도로 사소 할 수 있습니다.
답변
적절한 GetHashCode로 Tuple을 재정의하고 키로 사용합니다.
적절한 메서드를 오버로드하는 한 괜찮은 성능을 볼 수 있습니다.
답변
다음은 참조 용 .NET 튜플입니다.
[Serializable]
public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple {
private readonly T1 m_Item1;
private readonly T2 m_Item2;
private readonly T3 m_Item3;
public T1 Item1 { get { return m_Item1; } }
public T2 Item2 { get { return m_Item2; } }
public T3 Item3 { get { return m_Item3; } }
public Tuple(T1 item1, T2 item2, T3 item3) {
m_Item1 = item1;
m_Item2 = item2;
m_Item3 = item3;
}
public override Boolean Equals(Object obj) {
return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
}
Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
if (other == null) return false;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
return false;
}
return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3);
}
Int32 IComparable.CompareTo(Object obj) {
return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default);
}
Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) {
if (other == null) return 1;
Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>;
if (objTuple == null) {
throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other");
}
int c = 0;
c = comparer.Compare(m_Item1, objTuple.m_Item1);
if (c != 0) return c;
c = comparer.Compare(m_Item2, objTuple.m_Item2);
if (c != 0) return c;
return comparer.Compare(m_Item3, objTuple.m_Item3);
}
public override int GetHashCode() {
return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
}
Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) {
return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3));
}
Int32 ITuple.GetHashCode(IEqualityComparer comparer) {
return ((IStructuralEquatable) this).GetHashCode(comparer);
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
sb.Append("(");
return ((ITuple)this).ToString(sb);
}
string ITuple.ToString(StringBuilder sb) {
sb.Append(m_Item1);
sb.Append(", ");
sb.Append(m_Item2);
sb.Append(", ");
sb.Append(m_Item3);
sb.Append(")");
return sb.ToString();
}
int ITuple.Size {
get {
return 3;
}
}
}
