[英]How to retrieve actual item from 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)。 从HashSet<T>
检索项目的唯一方法是枚举所有 O(n) 的项目。
除了制作我自己的HashSet<T>
或使用Dictionary<K, V>
我还没有找到任何解决此问题的方法。 还有其他想法吗?
笔记:
我不想检查HashSet<T>
包含该项目。 我想获取对存储在HashSet<T>
的项目的引用,因为我需要更新它(而不用另一个实例替换它)。 我将传递给TryGetItem
将是相等的(根据我传递给构造函数的比较机制),但它不会是相同的引用。
这实际上是集合中的一个巨大遗漏。 您将只需要一个键字典或一个允许检索对象引用的 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 Core 中, 最近添加到 .NET 4.7.2 中:
在 .NET Framework 4.7.2 中,我们向标准集合类型添加了一些 API,这些 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);
PS .:如果您有兴趣,他们将来会添加相关功能- HashSet.GetOrAdd(T)。
此方法已添加到.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";
另一个技巧是通过访问 HashSet 的内部函数InternalIndexOf
来进行反射。 请记住,字段名称是硬编码的,因此如果在即将发布的 .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);
}
现在 .NET Core 2.0 有这个确切的方法。
修改了@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;
}
}
在这种情况下,SortedSet 可能会有 O(log n) 的查找时间,如果使用它是一种选择。 仍然不是 O(1),但至少更好。
HashSet 有一个Contains(T)方法。
如果您需要自定义比较方法(例如,存储人员对象,但使用 SSN 进行相等比较),您可以指定IEqualityComparer 。
您还可以使用 ToList() 方法并对其应用索引器。
HashSet<string> mySet = new HashSet();
mySet.Add("mykey");
string key = mySet.toList()[0];
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.