F # Функция сравнения, связанная с массивами - PullRequest
0 голосов
/ 09 апреля 2020

Этот пост относится к предыдущему .

Я считаю, что F # хранит где-то метод, позволяющий сравнивать массивы одного типа, при условии, что элементы массивов сопоставимыми. Причина, по которой я так считаю: тип Set требует, чтобы его элементы были сопоставимы, а Set принимает массивы сопоставимых типов (он даже упорядочивает их элементы при создании экземпляра, что позволяет нам угадать функцию сравнения, которую он использует для массивов). Несколько примеров:

let s1 = [| [| 10; 20 |]; [| 10; 19 |]; [| 10; 19; 100 |]; [| 10; 20; -100 |] |] |> Set.ofArray;;

возвращает

val s1 : Set<int []> =  set [[|10; 19|]; [|10; 20|]; [|10; 19; 100|]; [|10; 20; -100|]]

аналогично,

let s2 = [| [| ("b", 1); ("a", 2) |]; [| ("z", 1) |]; [| ("a", 0); ("a", 0); ("a", 0) |]; [| ("b", 1); ("a", 3) |] |] |> Set.ofArray;;

возвращает

val s2 : Set<(string * int) []> = set [[|("z", 1)|]; [|("b", 1); ("a", 2)|]; [|("b", 1); ("a", 3)|]; [|("a", 0); ("a", 0); ("a", 0)|]]

(сравнение выглядит так: Сравнение элементов слева направо, если два массива имеют одинаковую длину, или arr1> arr2, если arr1.Length> arr2.Length)

Последний, более сложный, пример:

type Foo = Z of int | A of int
let s3 = [| [| Z 1; A 1|]; [| A 100 |]; [| Z 1; Z 20|]; [| Z 0; Z 0; Z 0 |]; [| Z 1; Z 10|] |] |> Set.ofArray;;

возвращает

val s3 : Set<Foo []> = set [[|A 100|]; [|Z 1; Z 10|]; [|Z 1; Z 20|]; [|Z 1; A 1|]; [|Z 0; Z 0; Z 0|]]`

Однако последний пример не работает, поскольку объекты несопоставимы:

let s0 = [| [| box 10; box 20 |]; [| box 10; box 19 |] |] |> Set.ofArray;;

возвращает

stdin(52,62): error FS0001: The type 'obj' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface`

Так что я надеюсь, что приведенное выше показывает этот Set знает, как сравнивать массивы (при условии, что тип их элементов сопоставим).

К сожалению, я не могу получить доступ к методу сравнения двух массивов напрямую:

let x1 : int[] = [| 1 |]
let x2 : int[] = [| 2 |]
let c12 = x1.CompareTo(x2);;

Дает следующее сообщение об ошибке:

 let c12 = x1.CompareTo(x2);;
 -------------^^^^^^^^^
stdin(3,14): error FS0039: The field, constructor or member 'CompareTo' is not defined.

Мой вопрос: как получить доступ к методу CompareTo, связанному с объектом массива? ... Если возможно, через сигнатуру функции arrcompare : arr1:obj -> arr2:obj -> int или arrcompare : arr1:obj -> arr2:obj -> int option (используя option для случаев, когда 2 аргумента не являются массивами или не являются массивами одного типа).

Ответы [ 4 ]

2 голосов
/ 09 апреля 2020

Массив не реализует System.IComparable, в то время как набор делает. Это не означает, что массивы нельзя сравнивать; FSharp обеспечивает сравнение массивов по аналогии с истинным структурным сравнением, сравнивая их по рангу, длине и элементам.

[|1|] < [|2|]
// val it : bool = true

Это поведение описано в F # spe c, под заголовком Equality , Хеширование и сравнение в псевдокоде:

// Special types not supporting IComparable
| (:? Array as arr1), (:? Array as arr2) ->
    ... compare the arrays by rank, lengths and elements ...

Ваша проблема, по-видимому, связана с попыткой сравнить вещи, которые в принципе нельзя сравнивать, поскольку они не относятся к одному и тому же типу. Возможно, вам следует переосмыслить свой подход, вместо отдельных реализаций интерфейса для различных несовместимых массивов, вы можете создать тип суммы и реализовать System.IComparable для этого.

1 голос
/ 09 апреля 2020

Ну, сравнение двух IComparable довольно просто. Остальные являются коллекциями объектов, и IEnumerable должны составлять Array, IList, ICollection, которые мы, вероятно, можем сравнивать по порядку.

А остальные - кортежи, которые, если они этого не делают реализовать IEnumerable, как ожидается, реализовать ITuple.

let rec compareAny (o1 : obj) (o2:obj) = 
    match (o1, o2) with
    | (:? IComparable as o1), (:? IComparable as o2) 
         -> Some(compare o1 o2)
    | (:? IEnumerable as arr1), (:? IEnumerable as arr2) ->      
        Seq.zip (arr1 |> Seq.cast) (arr2 |> Seq.cast)
        |> Seq.choose(fun (a, b) -> compareAny a b)
        |> Seq.skipWhile ((=) 0)
        |> Seq.tryHead
        |> Option.defaultValue 0
        |> Some
    | (:? ITuple as tup1), (:? ITuple as tup2) ->
        let tupleToSeq (tuple: ITuple) = 
            seq { for i in 0..tuple.Length do yield tuple.[i] }
        compareAny (tupleToSeq tup1) (tupleToSeq tup2)
    | _ -> None

Объяснение сравнения IEnumerable выглядит следующим образом:

Взять элементы из обоих | Если ни один из них не сопоставим, пропустите, в противном случае сравните | игнорировать все успешные тесты на равенство | найти первый элемент, для которого сравнение будет < или > | если его нет (например, пустой список, вернуть 0) | заверните как некоторые.

0 голосов
/ 10 апреля 2020

Благодаря ответам Асти и Кафера, я смог немного покопаться и понял, что мне нужно структурное равенство, которое можно кодировать таким образом:

let rec structcompare (o1 : obj) (o2:obj) : int option = 
    match (o1, o2) with
    | (:? IStructuralComparable as arr1), (:? IStructuralComparable as arr2) -> 
           if arr1.GetType() = arr2.GetType() then compare arr1 arr2 |> Some else None
    | _ -> None

со следующими результатами :

structcompare (box [| 10; 19 |]) (box [| 10; 20 |]);;

возвращает val it : int option = Some -1

structcompare (box [| 10; 19; 0 |]) (box [| 10; 20 |]);;

возвращает val it : int option = Some 1

и

structcompare (box [| 1 |]) (box [| "a" |]);;

возвращает val it : int option = None

0 голосов
/ 09 апреля 2020

Вы можете просто использовать оператор равенства: (=)

...