如何从HashSet <t>中检索实际项目?</t>

时间:2011-10-13 21:00:09

标签: c# .net hashset

我已阅读this question关于无法解决的原因,但未找到问题的解决方案。

我想从.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)。从HashSet<T>检索项目的唯一方法是枚举所有O(n)项。

除了制作我自己的HashSet<T>或使用Dictionary<K, V>之外,我还没有找到解决此问题的方法。还有其他想法吗?

注意:
我不想检查HashSet<T>是否包含该项目。我想获得对HashSet<T>中存储的项的引用,因为我需要更新它(不用另一个实例替换它)。我将传递给TryGetItem的项目将是相等的(根据我传递给构造函数的比较机制),但它不会是相同的引用。

11 个答案:

答案 0 :(得分:57)

这实际上是集合中的一个巨大遗漏。您只需要一个Keys of Dictionary或一个允许检索对象引用的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

答案 1 :(得分:11)

此方法已添加到.NET Framework 4.7.2(和.NET Core 2.0之前);见HashSet<T>.TryGetValue。引用the source

/// <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)

答案 2 :(得分:10)

您要求的内容已添加到.NET Core a year ago,并且是recently added to .NET 4.7.2

    In .NET Framework 4.7.2 we have added a few APIs to the standard Collection types
 that will enable new functionality as follows.  ‘TryGetValue‘ is added to SortedSet
 and HashSet to match the Try pattern used in other collection types.

签名如下(在.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);

P.S :如果您有兴趣,可以使用related function they're adding in the future - HashSet.GetOrAdd(T)。

答案 3 :(得分:5)

重载字符串相等比较器怎么样:

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

答案 4 :(得分:3)

通过访问HashSet的内部函数InternalIndexOf,另一个Trick会做Reflection。请记住,字段名称是硬编码的,因此如果在即将发布的.NET版本中进行更改,这将会中断。 以下解决方案仅支持一种特定类型(例如字符串),但我猜也可以是通用的。

public static class Extensions
{
    private static Func<HashSet<string>, string, string> getHashSetInternalValue;

    static Extensions()
    {
        ParameterExpression targetExp = Expression.Parameter(typeof(HashSet<string>), "target");
        ParameterExpression itemExp = Expression.Parameter(typeof(string), "item");

        var slotsExp = Expression.Field(targetExp, typeof(HashSet<string>).GetField("m_slots", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));

        var indexExp = Expression.Call(targetExp, typeof(HashSet<string>).GetMethod("InternalIndexOf", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance), itemExp);

        var slotExp = Expression.ArrayAccess(slotsExp, indexExp);
        var valueExp = Expression.Field(slotExp, "value");

        var testExp = Expression.GreaterThanOrEqual(indexExp, Expression.Constant(0));
        var conditionExp = Expression.Condition(testExp, valueExp, Expression.Constant(null, typeof(string)));

        getHashSetInternalValue = Expression.Lambda<Func<HashSet<string>, string, string>>(conditionExp, new[] { targetExp, itemExp }).Compile();
    }

    /// <summary>
    /// Gets the internal item value equal to <paramref name="item"/> or null if <paramref name="item"/> is not contained
    /// </summary>
    public static string GetInternalValue(this HashSet<string> hashet, string item)
    {
        return getHashSetInternalValue(hashet, item);
    }
}

测试:

var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "TABLE" };
var intern = set.GetInternalValue("table"); 

Console.WriteLine(intern); // prints "TABLE"

答案 5 :(得分:2)

好的,你可以这样做

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

这是为了获取所选对象的新实例。为了更新您的对象,您应该使用:

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

答案 6 :(得分:2)

现在,.NET Core 2.0具有这种确切的方法。

enter image description here

答案 7 :(得分:1)

如果使用该选项,SortedSet可能在该情况下具有O(log n)查找时间。仍然不是O(1),但至少更好。

答案 8 :(得分:1)

修改了@ mp666的实现,因此它可以用于任何类型的HashSet,并允许覆盖默认的相等比较器。

public interface IRetainingComparer<T> : IEqualityComparer<T>
{
    T Key { get; }
    void ClearKeyCache();
}

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter
/// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>).
/// </summary>
/// <typeparam name="T">The type of object being compared.</typeparam>
/// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks>
public class RetainingEqualityComparerObject<T> : IRetainingComparer<T> where T : class
{
    private readonly IEqualityComparer<T> _comparer;

    [ThreadStatic]
    private static WeakReference<T> _retained;

    public RetainingEqualityComparerObject(IEqualityComparer<T> comparer)
    {
        _comparer = comparer;
    }

    /// <summary>
    /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'.
    /// </summary>
    /// <remarks>Uses a <see cref="WeakReference{T}"/> so unintended memory leaks are not encountered.</remarks>
    public T Key
    {
        get
        {
            T retained;
            return _retained == null ? null : _retained.TryGetTarget(out retained) ? retained : null;
        }
    }


    /// <summary>
    /// Sets the retained <see cref="Key"/> to the default value.
    /// </summary>
    /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks>
    public void ClearKeyCache()
    {
        _retained = _retained ?? new WeakReference<T>(null);
        _retained.SetTarget(null);
    }

    /// <summary>
    /// Test two objects of type <see cref="T"/> for equality retaining the object if successful.
    /// </summary>
    /// <param name="a">An instance of <see cref="T"/>.</param>
    /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param>
    /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns>
    public bool Equals(T a, T b)
    {
        if (!_comparer.Equals(a, b))
        {
            return false;
        }

        _retained = _retained ?? new WeakReference<T>(null);
        _retained.SetTarget(a);
        return true;
    }

    /// <summary>
    /// Gets the hash code value of an instance of <see cref="T"/>.
    /// </summary>
    /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param>
    /// <returns>The hash code value from <paramref name="o"/>.</returns>
    public int GetHashCode(T o)
    {
        return _comparer.GetHashCode(o);
    }
}

/// <summary>
/// An <see cref="IEqualityComparer{T}"/> that retains the last key that successfully passed <see cref="IEqualityComparer{T}.Equals(T,T)"/>.
/// This class relies on the fact that <see cref="HashSet{T}"/> calls the <see cref="IEqualityComparer{T}.Equals(T,T)"/> with the first parameter
/// being an existing element and the second parameter being the one passed to the initiating call to <see cref="HashSet{T}"/> (eg. <see cref="HashSet{T}.Contains(T)"/>).
/// </summary>
/// <typeparam name="T">The type of object being compared.</typeparam>
/// <remarks>This class is thread-safe but may should not be used with any sort of parallel access (PLINQ).</remarks>
public class RetainingEqualityComparerStruct<T> : IRetainingComparer<T> where T : struct 
{
    private readonly IEqualityComparer<T> _comparer;

    [ThreadStatic]
    private static T _retained;

    public RetainingEqualityComparerStruct(IEqualityComparer<T> comparer)
    {
        _comparer = comparer;
    }

    /// <summary>
    /// The retained instance on side 'a' of the <see cref="Equals"/> call which successfully met the equality requirement agains side 'b'.
    /// </summary>
    public T Key => _retained;


    /// <summary>
    /// Sets the retained <see cref="Key"/> to the default value.
    /// </summary>
    /// <remarks>This should be called prior to performing an operation that calls <see cref="Equals"/>.</remarks>
    public void ClearKeyCache()
    {
        _retained = default(T);
    }

    /// <summary>
    /// Test two objects of type <see cref="T"/> for equality retaining the object if successful.
    /// </summary>
    /// <param name="a">An instance of <see cref="T"/>.</param>
    /// <param name="b">A second instance of <see cref="T"/> to compare against <paramref name="a"/>.</param>
    /// <returns>True if <paramref name="a"/> and <paramref name="b"/> are equal, false otherwise.</returns>
    public bool Equals(T a, T b)
    {
        if (!_comparer.Equals(a, b))
        {
            return false;
        }

        _retained = a;
        return true;
    }

    /// <summary>
    /// Gets the hash code value of an instance of <see cref="T"/>.
    /// </summary>
    /// <param name="o">The instance of <see cref="T"/> to obtain a hash code from.</param>
    /// <returns>The hash code value from <paramref name="o"/>.</returns>
    public int GetHashCode(T o)
    {
        return _comparer.GetHashCode(o);
    }
}

/// <summary>
/// Provides TryGetValue{T} functionality similar to that of <see cref="IDictionary{TKey,TValue}"/>'s implementation.
/// </summary>
public class ExtendedHashSet<T> : HashSet<T>
{
    /// <summary>
    /// This class is guaranteed to wrap the <see cref="IEqualityComparer{T}"/> with one of the <see cref="IRetainingComparer{T}"/>
    /// implementations so this property gives convenient access to the interfaced comparer.
    /// </summary>
    private IRetainingComparer<T> RetainingComparer => (IRetainingComparer<T>)Comparer;

    /// <summary>
    /// Creates either a <see cref="RetainingEqualityComparerStruct{T}"/> or <see cref="RetainingEqualityComparerObject{T}"/>
    /// depending on if <see cref="T"/> is a reference type or a value type.
    /// </summary>
    /// <param name="comparer">(optional) The <see cref="IEqualityComparer{T}"/> to wrap. This will be set to <see cref="EqualityComparer{T}.Default"/> if none provided.</param>
    /// <returns>An instance of <see cref="IRetainingComparer{T}"/>.</returns>
    private static IRetainingComparer<T> Create(IEqualityComparer<T> comparer = null)
    {
        return (IRetainingComparer<T>) (typeof(T).IsValueType ? 
            Activator.CreateInstance(typeof(RetainingEqualityComparerStruct<>)
                .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default)
            :
            Activator.CreateInstance(typeof(RetainingEqualityComparerObject<>)
                .MakeGenericType(typeof(T)), comparer ?? EqualityComparer<T>.Default));
    }

    public ExtendedHashSet() : base(Create())
    {
    }

    public ExtendedHashSet(IEqualityComparer<T> comparer) : base(Create(comparer))
    {
    }

    public ExtendedHashSet(IEnumerable<T> collection) : base(collection, Create())
    {
    }

    public ExtendedHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) : base(collection, Create(comparer))
    {
    }

    /// <summary>
    /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>.
    /// </summary>
    /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param>
    /// <param name="original">
    /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>.
    /// This will be set to null for reference types or default(T) for value types when no match found.
    /// </param>
    /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns>
    public bool TryGetValue(T value, out T original)
    {
        var comparer = RetainingComparer;
        comparer.ClearKeyCache();

        if (Contains(value))
        {
            original = comparer.Key;
            return true;
        }

        original = default(T);
        return false;
    }
}

public static class HashSetExtensions
{
    /// <summary>
    /// Attempts to find a key in the <see cref="HashSet{T}"/> and, if found, places the instance in <paramref name="original"/>.
    /// </summary>
    /// <param name="hashSet">The instance of <see cref="HashSet{T}"/> extended.</param>
    /// <param name="value">The key used to search the <see cref="HashSet{T}"/>.</param>
    /// <param name="original">
    /// The matched instance from the <see cref="HashSet{T}"/> which is not neccessarily the same as <paramref name="value"/>.
    /// This will be set to null for reference types or default(T) for value types when no match found.
    /// </param>
    /// <returns>True if a key in the <see cref="HashSet{T}"/> matched <paramref name="value"/>, False if no match found.</returns>
    /// <exception cref="ArgumentNullException">If <paramref name="hashSet"/> is null.</exception>
    /// <exception cref="ArgumentException">
    /// If <paramref name="hashSet"/> does not have a <see cref="HashSet{T}.Comparer"/> of type <see cref="IRetainingComparer{T}"/>.
    /// </exception>
    public static bool TryGetValue<T>(this HashSet<T> hashSet, T value, out T original)
    {
        if (hashSet == null)
        {
            throw new ArgumentNullException(nameof(hashSet));
        }

        if (hashSet.Comparer.GetType().IsInstanceOfType(typeof(IRetainingComparer<T>)))
        {
            throw new ArgumentException($"HashSet must have an equality comparer of type '{nameof(IRetainingComparer<T>)}' to use this functionality", nameof(hashSet));
        }

        var comparer = (IRetainingComparer<T>)hashSet.Comparer;
        comparer.ClearKeyCache();

        if (hashSet.Contains(value))
        {
            original = comparer.Key;
            return true;
        }

        original = default(T);
        return false;
    }
}

答案 9 :(得分:-2)

HashSet有一个包含(T)方法。

如果需要自定义比较方法(例如,存储人物对象,但使用SSN进行相等比较),则可以指定 IEqualityComparer

答案 10 :(得分:-11)

您还可以使用ToList()方法并将索引器应用于该方法。

HashSet<string> mySet = new HashSet();
mySet.Add("mykey");
string key = mySet.toList()[0];