Mar*_*tos 125 .net linq delegates
几个Linq.Enumerable函数需要一个IEqualityComparer<T>.是否有一个方便的包装类适应delegate(T,T)=>bool实现IEqualityComparer<T>?编写一个很容易(如果你忽略了定义正确的哈希码的问题),但我想知道是否有开箱即用的解决方案.
具体来说,我想对Dictionarys 进行集合操作,仅使用Keys来定义成员资格(同时根据不同的规则保留值).
Dan*_*Tao 169
GetHashCode其他人已经评论过这样一个事实:任何自定义IEqualityComparer<T>实现都应该包含一个GetHashCode方法 ; 但是没有人愿意在任何细节上解释原因.
这就是原因.您的问题特别提到了LINQ扩展方法; 几乎所有这些都依赖哈希码来正常工作,因为它们在内部利用哈希表来提高效率.
就拿Distinct,例如.如果所有使用的Equals方法都是一种方法,请考虑这种扩展方法的含义.如果您只有一个项目已按顺序扫描,您如何确定Equals?您枚举了您已查看的整个值集合并检查匹配项.这将导致Distinct使用最坏情况的O(N 2)算法而不是O(N )算法!
幸运的是,事实并非如此.Distinct不只是使用Equals; 它也用GetHashCode.事实上,如果没有提供适当的产品,它绝对不能正常工作IEqualityComparer<T>GetHashCode.下面是一个说明这一点的人为例子.
说我有以下类型:
class Value
{
public string Name { get; private set; }
public int Number { get; private set; }
public Value(string name, int number)
{
Name = name;
Number = number;
}
public override string ToString()
{
return string.Format("{0}: {1}", Name, Number);
}
}
Run Code Online (Sandbox Code Playgroud)
现在说我有一个List<Value>,我想找到所有具有不同名称的元素.这是Distinct使用自定义相等比较器的完美用例.因此,让我们使用Comparer<T>类从阿库的回答:
var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);
Run Code Online (Sandbox Code Playgroud)
现在,如果我们有一堆Value具有相同Name属性的元素,它们应该全部折叠成一个返回的值Distinct,对吧?让我们来看看...
var values = new List<Value>();
var random = new Random();
for (int i = 0; i < 10; ++i)
{
values.Add("x", random.Next());
}
var distinct = values.Distinct(comparer);
foreach (Value x in distinct)
{
Console.WriteLine(x);
}
Run Code Online (Sandbox Code Playgroud)
输出:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377
嗯,这没用,是吗?
怎么样GroupBy?我们试试看:
var grouped = values.GroupBy(x => x, comparer);
foreach (IGrouping<Value> g in grouped)
{
Console.WriteLine("[KEY: '{0}']", g);
foreach (Value x in g)
{
Console.WriteLine(x);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
[KEY = 'x: 1346013431'] x: 1346013431 [KEY = 'x: 1388845717'] x: 1388845717 [KEY = 'x: 1576754134'] x: 1576754134 [KEY = 'x: 1104067189'] x: 1104067189 [KEY = 'x: 1144789201'] x: 1144789201 [KEY = 'x: 1862076501'] x: 1862076501 [KEY = 'x: 1573781440'] x: 1573781440 [KEY = 'x: 646797592'] x: 646797592 [KEY = 'x: 655632802'] x: 655632802 [KEY = 'x: 1206819377'] x: 1206819377
再说一遍:没有用.
如果你考虑一下,在内部Distinct使用HashSet<T>(或等效的),以及在内部GroupBy使用类似的东西是有意义的Dictionary<TKey, List<T>>.这可以解释为什么这些方法不起作用?我们试试这个:
var uniqueValues = new HashSet<Value>(values, comparer);
foreach (Value x in uniqueValues)
{
Console.WriteLine(x);
}
Run Code Online (Sandbox Code Playgroud)
输出:
x: 1346013431 x: 1388845717 x: 1576754134 x: 1104067189 x: 1144789201 x: 1862076501 x: 1573781440 x: 646797592 x: 655632802 x: 1206819377
是的......开始有意义吗?
希望从这些例子可以清楚地看出为什么GetHashCode在任何IEqualityComparer<T>实现中包含一个适当的实例是如此重要.
扩展orip的答案:
这里可以做一些改进.
Func<T, TKey>而不是Func<T, object>; 这将防止在实际keyExtractor本身中装入值类型键.where TKey : IEquatable<TKey>约束; 这将阻止在Equals调用中装箱(object.Equals接受一个object参数;你需要一个IEquatable<TKey>实现来获取TKey参数而不用装箱).显然,这可能会造成太严格的限制,因此您可以创建没有约束的基类和带有它的派生类.以下是生成的代码的外观:
public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
protected readonly Func<T, TKey> keyExtractor;
public KeyEqualityComparer(Func<T, TKey> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public virtual bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
where TKey : IEquatable<TKey>
{
public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
: base(keyExtractor)
{ }
public override bool Equals(T x, T y)
{
// This will use the overload that accepts a TKey parameter
// instead of an object parameter.
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
}
Run Code Online (Sandbox Code Playgroud)
ori*_*rip 118
当您想要自定义相等性检查时,99%的时间您有兴趣定义要比较的键,而不是比较本身.
这可能是一个优雅的解决方案(Python的列表排序方法的概念).
用法:
var foo = new List<string> { "abc", "de", "DE" };
// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );
Run Code Online (Sandbox Code Playgroud)
本KeyEqualityComparer类:
public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, object> keyExtractor;
public KeyEqualityComparer(Func<T,object> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
Run Code Online (Sandbox Code Playgroud)
aku*_*aku 48
我担心没有这样的包装盒开箱即用.然而,创建一个并不难:
class Comparer<T>: IEqualityComparer<T>
{
private readonly Func<T, T, bool> _comparer;
public Comparer(Func<T, T, bool> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public bool Equals(T x, T y)
{
return _comparer(x, y);
}
public int GetHashCode(T obj)
{
return obj.ToString().ToLower().GetHashCode();
}
}
...
Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));
Run Code Online (Sandbox Code Playgroud)
Rub*_*ink 43
通常情况下,我会通过在答案上评论@Sam来解决这个问题(我已经对原始帖子进行了一些编辑,以便在不改变行为的情况下对其进行清理.)
以下是我对@Sam 的答案的重复,对[IMNSHO]关键修复默认的散列策略: -
class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
Run Code Online (Sandbox Code Playgroud)
ldp*_*615 21
与丹涛的答案相同,但有一些改进:
依赖EqualityComparer<>.Default于进行实际比较,以避免对struct已实现的值类型进行装箱IEquatable<>.
自EqualityComparer<>.Default使用以来它不会爆炸null.Equals(something).
提供静态包装器IEqualityComparer<>,它将有一个静态方法来创建比较器实例 - 简化调用.相比
Equality<Person>.CreateComparer(p => p.ID);
Run Code Online (Sandbox Code Playgroud)
同
new EqualityComparer<Person, int>(p => p.ID);
Run Code Online (Sandbox Code Playgroud)添加了指定IEqualityComparer<>密钥的重载.
班级:
public static class Equality<T>
{
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
{
return CreateComparer(keySelector, null);
}
public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
return new KeyEqualityComparer<V>(keySelector, comparer);
}
class KeyEqualityComparer<V> : IEqualityComparer<T>
{
readonly Func<T, V> keySelector;
readonly IEqualityComparer<V> comparer;
public KeyEqualityComparer(Func<T, V> keySelector,
IEqualityComparer<V> comparer)
{
if (keySelector == null)
throw new ArgumentNullException("keySelector");
this.keySelector = keySelector;
this.comparer = comparer ?? EqualityComparer<V>.Default;
}
public bool Equals(T x, T y)
{
return comparer.Equals(keySelector(x), keySelector(y));
}
public int GetHashCode(T obj)
{
return comparer.GetHashCode(keySelector(obj));
}
}
}
Run Code Online (Sandbox Code Playgroud)
你可以像这样使用它:
var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);
Run Code Online (Sandbox Code Playgroud)
人是一个简单的类:
class Person
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
小智 11
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
readonly Func<T, T, bool> _comparer;
readonly Func<T, int> _hash;
public FuncEqualityComparer( Func<T, T, bool> comparer )
: this( comparer, t => t.GetHashCode())
{
}
public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
{
_comparer = comparer;
_hash = hash;
}
public bool Equals( T x, T y )
{
return _comparer( x, y );
}
public int GetHashCode( T obj )
{
return _hash( obj );
}
}
Run Code Online (Sandbox Code Playgroud)
随着扩展: -
public static class SequenceExtensions
{
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
}
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
{
return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
}
}
Run Code Online (Sandbox Code Playgroud)
orip的答案很棒.
这里有一个小扩展方法,使它更容易:
public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object> keyExtractor)
{
return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
44238 次 |
| 最近记录: |