有没有办法覆盖F#set中的比较函数?
我没有看到任何采用IComparer<T>
或比较函数的设置构造函数:
Set.ofSeq
等不采取比较功能FSharpSet(IComparer<T> comparer, SetTree<T> tree)
构造函数是内部的,因为SetTreeModule.ofSeq<a>(IComparer<a> comparer, IEnumerable<a> c)
显然也是内部的。我的实际问题是我有一组('a * 'a)
,我希望进行比较,例如(1,3)=(3,1)。
我知道我可以在实现IComparable<T>
的类型中包装它,但是有什么方法可以避免这种情况吗?
答案 0 :(得分:18)
我认为F#核心库中提供的set
类型不允许指定自己的比较。但是,PowerPack中有一个标记的版本允许这样做。下面显示如何使用比较器创建一个使用模10比较整数的集合:
#r @"FSharp.PowerPack.dll"
open System.Collections.Generic
open Microsoft.FSharp.Collections.Tagged
type MyComparer() =
interface IComparer<int> with
member x.Compare(a, b) = (a % 10).CompareTo(b % 10)
// Type alias for a set that uses 'MyComparer'
type MySet = Tagged.Set<int, MyComparer>
let comparer = new MyComparer()
let s1 = MySet.Create(comparer, [1; 2; 3])
let s2 = MySet.Create(comparer, [11; 14])
MySet.Union(s1, s2)
答案 1 :(得分:5)
您需要按照建议使用“包装”类型。
(对于像F#Set
和Map
这样的不可变类型,自定义比较器存在API问题。由于每个操作都返回一个新的Set
对象,因此新对象需要共享比较器(可能导致线程问题),并没有好的方法来决定结果应该用什么比较器,例如两个具有不同比较器的Set.union
的结果。所以Set
和{{1通过始终直接在元素类型上使用比较来避免混淆。正如Tomas所提到的,PowerPack有一个替代API,它使比较器成为该类型的一部分,从而实现了类型安全/比较安全的Map
方法,例如。)
答案 2 :(得分:1)
我有类似的问题。我也被限制只使用标准库而没有外部依赖。与此同时,我想利用我已经拥有的IEqualityComparer
和IComparer
接口的现有实现。
所以,我最终创建了自己的包装器结构,现在我可以使用F#的内置Map和Set:
open System
[<Struct>]
[<CustomComparison>]
[<CustomEquality>]
type ComparisonAdapter<'T>(value: 'T, comparer: IComparer<'T>, eqComparer: IEqualityComparer<'T>) =
new(value) = ComparisonAdapter(value, Comparer<'T>.Default, EqualityComparer<'T>.Default)
member this.CompareTo (cmp: IComparer<'T>, v: 'T) = cmp.Compare(v, value)
member this.CompareTo (v: 'T) = this.CompareTo(comparer, v)
member this.CompareTo (c: ComparisonAdapter<'T>) = c.CompareTo(comparer, value)
member this.CompareTo (o: obj) =
match o with
| :? ComparisonAdapter<'T> as ca -> this.CompareTo(ca)
| :? 'T as t -> this.CompareTo(t)
| :? IComparable as cmp -> (-1)*(cmp.CompareTo(value))
| _ -> raise (NotSupportedException ())
member this.Equals (c: ComparisonAdapter<'T>): bool = c.Equals(eqComparer, value)
member this.Equals (cmp: IEqualityComparer<'T>, v: 'T): bool = cmp.Equals(v, value)
member this.Equals (v: 'T): bool = eqComparer.Equals(v, value)
override this.Equals (o: obj): bool =
if (o :? ComparisonAdapter<'T>) then this.Equals(downcast o: ComparisonAdapter<'T>)
else if (o :? 'T) then this.Equals(downcast o: 'T)
else false
override this.GetHashCode () = eqComparer.GetHashCode value
member this.Value with get () = value
interface IEquatable<'T> with member this.Equals other = this.Equals(eqComparer, other)
interface IComparable<'T> with member this.CompareTo other = this.CompareTo(comparer, other)
interface IComparable with member this.CompareTo o = this.CompareTo o
我使用它的方法是创建内置集合的包装器并在内部创建键/项包装器,以使用户代码更友好。所以我会有像
这样的东西type MyCustomMap<'TKey, 'TValue>(cmp: IComparer<'TKey>, eq: IEqualityComparer<'TKey>) =
let innerMap = new Map<ComparisonAdapter<'TKey>, 'TValue>()
member this Add key value =
let actualKey = new ComparisonAdapter<'TKey>(key, cmp, eq)
innerMap.Add actualKey value
// * other map manipulation members omitted for brevity *
您也可以使用现有地图并进行设置,但是当您调用相应的Add
/ Remove
/ TryFind
等方法时,您必须为每个键创建适配器。我更喜欢使用类似上面的集合包装器,因为这可以保证每个键都具有相同的比较器实现,并且用户代码不能篡改它们。