Сравнение переопределения для набора F #
Есть ли способ переопределить функцию сравнения в наборе F #?
Я не вижу никаких функций построения, которые выполняют функцию 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>
, но есть ли способ избежать этого?
Ответы
Ответ 1
Я думаю, что тип set
, доступный в основных библиотеках F #, не позволяет указать ваше собственное сравнение. Однако в PowerPack есть тегированная версия типа, которая позволяет это. Ниже показано, как создать набор с помощью сравнения, который сравнивает целые числа, используя modulo 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)
Ответ 2
Вам следует использовать тип "обертка", который вы предлагаете.
(Для неизменяемых типов, таких как F # Set
и Map
, существуют проблемы API с настраиваемыми компараторами. Поскольку каждая операция возвращает, например, новый объект Set
, новому объекту необходимо предоставить общий доступ к сопоставлению (возможно, проблемы), и нет хорошего способа решить, какой компаратор должен использовать результат, например, результат Set.union
двух наборов с разными сопоставителями. Таким образом, Set
и Map
избегают путаницы здесь, всегда используя сравнение непосредственно на тип элемента. Как упоминает Tomas, PowerPack имеет альтернативный API, который создает часть сравнения этого типа, которая позволяет, например, использовать метод typeafe/compare-safe Union
.)
Ответ 3
У меня была аналогичная проблема. Мне также было запрещено использовать только стандартные библиотеки и внешние зависимости. В то же время я хотел использовать существующие реализации интерфейсов IEqualityComparer
и IComparer
, которые у меня уже были в наличии.
Итак, я закончил создание собственной структуры-оболочки, которую теперь я могу использовать с встроенной картой F # и Set:
[<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) =
if (o :? Comparison<'T>) then this.CompareTo(downcast o: ComparisonAdapter<'T>)
else if (o :? 'T) then this.CompareTo(downcast o: 'T)
else if (o :? IComparable) then ((downcast o: IComparable)).CompareTo(value)
else 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 :? Comparison<'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