覆盖F#set的比较

时间:2010-04-03 17:39:40

标签: f#

有没有办法覆盖F#set中的比较函数?

我没有看到任何采用IComparer<T>或比较函数的设置构造函数:

  • Set.ofSeq等不采取比较功能
  • FSharpSet(IComparer<T> comparer, SetTree<T> tree)构造函数是内部的,因为
  • SetTree是内部的,
  • SetTreeModule.ofSeq<a>(IComparer<a> comparer, IEnumerable<a> c)显然也是内部的。

我的实际问题是我有一组('a * 'a),我希望进行比较,例如(1,3)=(3,1)。

我知道我可以在实现IComparable<T>的类型中包装它,但是有什么方法可以避免这种情况吗?

3 个答案:

答案 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#SetMap这样的不可变类型,自定义比较器存在API问题。由于每个操作都返回一个新的Set对象,因此新对象需要共享比较器(可能导致线程问题),并没有好的方法来决定结果应该用什么比较器,例如两个具有不同比较器的Set.union的结果。所以Set和{{1通过始终直接在元素类型上使用比较来避免混淆。正如Tomas所提到的,PowerPack有一个替代API,它使比较器成为该类型的一部分,从而实现了类型安全/比较安全的Map方法,例如。)

答案 2 :(得分:1)

我有类似的问题。我也被限制只使用标准库而没有外部依赖。与此同时,我想利用我已经拥有的IEqualityComparerIComparer接口的现有实现。

所以,我最终创建了自己的包装器结构,现在我可以使用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等方法时,您必须为每个键创建适配器。我更喜欢使用类似上面的集合包装器,因为这可以保证每个键都具有相同的比较器实现,并且用户代码不能篡改它们。