[c#] HashSet <T>에서 실제 항목을 검색하는 방법은 무엇입니까?

왜 불가능한 지에 대한 이 질문을 읽었 지만 문제에 대한 해결책을 찾지 못했습니다.

.NET에서 항목을 검색하고 싶습니다 HashSet<T>. 이 서명이있는 방법을 찾고 있습니다.

/// <summary>
/// Determines if this set contains an item equal to <paramref name="item"/>, 
/// according to the comparison mechanism that was used when the set was created. 
/// The set is not changed. If the set does contain an item equal to 
/// <paramref name="item"/>, then the item from the set is returned.
/// </summary>
bool TryGetItem<T>(T item, out T foundItem);

이러한 방법으로 항목 집합을 검색하면 O (1)이됩니다. a에서 항목을 검색하는 유일한 방법 HashSet<T>은 O (n) 인 모든 항목을 열거하는 것입니다.

본인은이 문제 다음 내 자신을 만들기 위해 어떤 해결 방법을 찾을 수있다 HashSet<T>또는를 사용합니다 Dictionary<K, V>. 다른 생각은 없나요?

참고 : 에 항목
HashSet<T>포함되어 있는지 확인하고 싶지 않습니다 . 항목 HashSet<T>을 업데이트해야하므로에 저장된 항목에 대한 참조를 가져오고 싶습니다 (다른 인스턴스로 대체하지 않음). 내가 전달할 항목은 TryGetItem동일하지만 (생성자에게 전달한 비교 메커니즘에 따라) 동일한 참조가 아닙니다.



답변

요청하신 내용은 1 년 전에 .NET Core 에 추가 되었으며 최근 .NET 4.7.2에 추가되었습니다 .

.NET Framework 4.7.2에서는 다음과 같이 새로운 기능을 활성화하는 몇 가지 API를 표준 컬렉션 유형에 추가했습니다.
– ‘TryGetValue’가 SortedSet 및 HashSet에 추가되어 다른 컬렉션 유형에서 사용되는 Try 패턴과 일치합니다.

서명은 다음과 같습니다 (.NET 4.7.2 이상에 있음).

    //
    // Summary:
    //     Searches the set for a given value and returns the equal value it finds, if any.
    //
    // Parameters:
    //   equalValue:
    //     The value to search for.
    //
    //   actualValue:
    //     The value from the set that the search found, or the default value of T when
    //     the search yielded no match.
    //
    // Returns:
    //     A value indicating whether the search was successful.
    public bool TryGetValue(T equalValue, out T actualValue);

추신 : 관심이 있으시면 앞으로 추가 할 관련 기능이 있습니다 -HashSet.GetOrAdd (T).


답변

이것은 실제로 컬렉션 세트에서 큰 누락입니다. 키 사전 또는 객체 참조 검색을 허용하는 HashSet이 필요합니다. 너무 많은 사람들이 그것을 요구 해왔고, 왜 그것이 고쳐지지 않는지는 저를 넘어선 것입니다.

타사 라이브러리가 없으면 Dictionary<T, T>Dictionary가 항목을 해시 테이블로 저장하기 때문에 값과 동일한 키 를 사용하는 것이 가장 좋은 해결 방법입니다 . 성능면에서는 HashSet과 동일하지만 물론 메모리를 낭비합니다 (항목 당 포인터 크기).

Dictionary<T, T> myHashedCollection;
...
if(myHashedCollection.ContainsKey[item])
    item = myHashedCollection[item]; //replace duplicate
else
    myHashedCollection.Add(item, item); //add previously unknown item
...
//work with unique item


답변

이 메서드는 .NET Framework 4.7.2 (및 이전 .NET Core 2.0 )에 추가되었습니다. 참조하십시오 HashSet<T>.TryGetValue. 출처 인용 :

/// <summary>
/// Searches the set for a given value and returns the equal value it finds, if any.
/// </summary>
/// <param name="equalValue">The value to search for.
/// </param>
/// <param name="actualValue">
/// The value from the set that the search found, or the default value
/// of <typeparamref name="T"/> when the search yielded no match.</param>
/// <returns>A value indicating whether the search was successful.</returns>
/// <remarks>
/// This can be useful when you want to reuse a previously stored reference instead of 
/// a newly constructed one (so that more sharing of references can occur) or to look up
/// a value that has more complete data than the value you currently have, although their
/// comparer functions indicate they are equal.
/// </remarks>
public bool TryGetValue(T equalValue, out T actualValue)


답변

문자열 같음 비교자를 오버로드하는 것은 어떻습니까?

  class StringEqualityComparer : IEqualityComparer<String>
{
    public string val1;
    public bool Equals(String s1, String s2)
    {
        if (!s1.Equals(s2)) return false;
        val1 = s1;
        return true;
    }

    public int GetHashCode(String s)
    {
        return s.GetHashCode();
    }
}
public static class HashSetExtension
{
    public static bool TryGetValue(this HashSet<string> hs, string value, out string valout)
    {
        if (hs.Contains(value))
        {
            valout=(hs.Comparer as StringEqualityComparer).val1;
            return true;
        }
        else
        {
            valout = null;
            return false;
        }
    }
}

그런 다음 HashSet을 다음과 같이 선언하십시오.

HashSet<string> hs = new HashSet<string>(new StringEqualityComparer());


답변

자, 이렇게 할 수 있습니다

YourObject x = yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault();

선택한 개체의 새 인스턴스를 가져 오는 것입니다. 개체를 업데이트하려면 다음을 사용해야합니다.

yourHashSet.Where(w => w.Name.Contains("strin")).FirstOrDefault().MyProperty = "something";


답변

이제 .NET Core 2.0에는이 정확한 방법이 있습니다.

HashSet.TryGetValue (T, T) 메서드


답변

또 다른 트릭은 InternalIndexOfHashSet 의 내부 기능에 액세스하여 Reflection을 수행 합니다. 필드 이름은 하드 코딩되므로 향후 .NET 버전에서 변경되면 중단됩니다.

참고 : Mono를 사용하는 경우 필드 이름을에서 m_slots로 변경해야 합니다 _slots.

internal static class HashSetExtensions<T>
{
    public delegate bool GetValue(HashSet<T> source, T equalValue, out T actualValue);

    public static GetValue TryGetValue { get; }

    static HashSetExtensions() {
        var targetExp = Expression.Parameter(typeof(HashSet<T>), "target");
        var itemExp   = Expression.Parameter(typeof(T), "item");
        var actualValueExp = Expression.Parameter(typeof(T).MakeByRefType(), "actualValueExp");

        var indexVar = Expression.Variable(typeof(int), "index");
        // ReSharper disable once AssignNullToNotNullAttribute
        var indexExp = Expression.Call(targetExp, typeof(HashSet<T>).GetMethod("InternalIndexOf", BindingFlags.NonPublic | BindingFlags.Instance), itemExp);

        var truePart = Expression.Block(
            Expression.Assign(
                actualValueExp, Expression.Field(
                    Expression.ArrayAccess(
                        // ReSharper disable once AssignNullToNotNullAttribute
                        Expression.Field(targetExp, typeof(HashSet<T>).GetField("m_slots", BindingFlags.NonPublic | BindingFlags.Instance)), indexVar),
                    "value")),
            Expression.Constant(true));

        var falsePart = Expression.Constant(false);

        var block = Expression.Block(
            new[] { indexVar },
            Expression.Assign(indexVar, indexExp),
            Expression.Condition(
                Expression.GreaterThanOrEqual(indexVar, Expression.Constant(0)),
                truePart,
                falsePart));

        TryGetValue = Expression.Lambda<GetValue>(block, targetExp, itemExp, actualValueExp).Compile();
    }
}

public static class Extensions
{
    public static bool TryGetValue2<T>(this HashSet<T> source, T equalValue,  out T actualValue) {
        if (source.Count > 0) {
            if (HashSetExtensions<T>.TryGetValue(source, equalValue, out actualValue)) {
                return true;
            }
        }
        actualValue = default;
        return false;
    }
}

테스트:

var x = new HashSet<int> { 1, 2, 3 };
if (x.TryGetValue2(1, out var value)) {
    Console.WriteLine(value);
}