Сравнение переопределений для набора F # - PullRequest
13 голосов
/ 03 апреля 2010

Есть ли способ переопределить функцию сравнения в наборе 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>, но есть ли способ избежать этого?

Ответы [ 3 ]

18 голосов
/ 03 апреля 2010

Я думаю, что тип set, доступный в основных библиотеках F #, не позволяет задавать ваше собственное сравнение. Однако в 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)
5 голосов
/ 03 апреля 2010

Вам нужно использовать тип «обертка», как вы предлагаете.

(Для неизменяемых типов, таких как F # Set и Map, существуют проблемы API с пользовательскими компараторами. Поскольку каждая операция возвращает, например, новый Set объект, новый объект должен совместно использовать компаратор (возможно, приводящий к поточной обработке) проблем), и нет хорошего способа решить, какой компаратор должен использовать результат, например, для результата Set.union двух наборов с разными компараторами. Таким образом, Set и Map позволяют избежать путаницы здесь, всегда используя сравнение непосредственно на тип элемента. Как упоминает Томас, в PowerPack есть альтернативный API, который делает компаратор частью типа, что, например, позволяет использовать метод безопасного типа / безопасного сравнения Union.)

1 голос
/ 19 мая 2017

У меня была похожая проблема. Я также был ограничен использованием только стандартных библиотек и никаких внешних зависимостей. В то же время я хотел использовать существующие реализации интерфейсов 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 и другие методы. Вместо этого я предпочитаю использовать оболочку коллекции, подобную приведенной выше, потому что это гарантирует, что каждый ключ будет иметь одинаковые реализации компаратора, а пользовательский код не сможет их изменить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...