F# 比较与数组关联的函数

F# Compare function associated to arrays

此 post 与 有关。

我相信 F# 在某处存储了一种允许比较相同类型数组的方法,前提是数组的元素是可比较的。我之所以这么认为:Set 类型要求其元素是可比较的,而 Set 接受可比较类型的数组(它甚至在实例化时对它们的元素进行排序,让我们可以猜测它用于数组的比较函数)。几个例子:

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

returns

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;;

returns

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;;

returns

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;;

returns

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 -> intarrcompare : arr1:obj -> arr2:obj -> int option(当 2 个参数不是数组或不是同一类型的数组时使用 option)。

您可以只使用相等运算符:(=)

数组不实现 System.IComparable,而集合实现。这并不意味着不能比较数组。 FSharp 确实提供了类似于真正结构比较的数组比较,按等级、长度和元素进行比较。

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

此行为在 F# 规范中描述,在标题 Equality, Hashing,and Comparison 下,伪代码:

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

您的问题似乎源于试图比较基本上无法比较的事物,因为它们不是同一类型。也许您应该重新考虑您的方法,而不是针对各种不兼容数组的单独接口实现,您可以创建一个求和类型并在其上实现 System.IComparable

嗯,比较两个 IComparable 非常简单。 其余的是对象的集合,IEnumerable 应该占 ArrayIListICollection,我们可能可以按顺序进行比较。

其余的是元组,如果它们不实现 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比较的解释如下:

从两者中取元素 | 如果两者都不可比较,则跳过,否则比较 | 忽略所有成功的平等测试 | 找到比较为 <> 的第一个元素 |如果有 none(例如,一个空列表,return 0)|包装成一些。

多亏了 Asti 和 Kaefer 的回答,我得以深入挖掘并意识到我需要的是结构相等性,可以这样编码:

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 |]);;

returns val it : int option = Some -1

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

returns val it : int option = Some 1

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

returns val it : int option = None