Я прочитал этот вопрос о том, почему это невозможно, но не нашел решения проблемы.
Я хотел бы получить элемент из .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
бы, будет равен (в соответствии с механизмом сравнения, который я передал конструктору), но это не будет та же ссылка.
Ответы:
То, о чем вы просите, было добавлено в .NET Core год назад , а недавно было добавлено в .NET 4.7.2 :
Подпись выглядит следующим образом (находится в .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).
источник
На самом деле это огромное упущение в наборе сборников. Вам понадобится либо Словарь ключей, либо 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());
источник
HashSet
Для предоставления конкретного преобразователя значений потребуется, чтобы кто-то создал экземпляр . Оптимальным решением было быTryGetValue
передать новый экземпляр специализированногоStringEqualityComparer
(в противном случае этоas StringEqualityComparer
могло бы привести к нулевому значению, вызывающему.val1
выброс свойства). При этом StringEqualityComparer может стать вложенным частным классом в HashSetExtension. Кроме того, в случае переопределенного компаратора равенства 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) Метод
источник
Другой трюк будет делать отражение, обращаясь к внутренней функции
InternalIndexOf
HashSet. Имейте в виду, что имена полей жестко запрограммированы, поэтому, если они изменятся в следующих версиях .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); }
источник
В этом случае у SortedSet, вероятно, будет время поиска O (log n), если это вариант. Все еще не O (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; } }
источник
Enumerable.Contains
, он перечислит все элементы набора и сравнит их, теряя любые преимущества, которые предоставляет хеш-реализация набора. Тогда вы можете просто написатьset.SingleOrDefault(e => set.Comparer.Equals(e, obj))
, который имеет те же характеристики и характеристики производительности, что и ваше решение.HashSet имеет метод Contains (T) .
Вы можете указать IEqualityComparer, если вам нужен собственный метод сравнения (например, сохранить объект человека, но использовать SSN для сравнения на равенство).
источник
Вы также можете использовать метод ToList () и применить к нему индексатор.
HashSet<string> mySet = new HashSet(); mySet.Add("mykey"); string key = mySet.toList()[0];
источник