F中的词典排序#

问题描述:

我正在玩玩具问题(凸面船体识别),需要两次字典排序。其中一个案例得到了type Point = { X: float; Y: float }的列表,我想用X坐标进行排序,如果相等,则用Y坐标进行排序。
我最后写了以下内容:F中的词典排序#

let rec lexiCompare comparers a b = 
    match comparers with 
    [ ] -> 0 
    | head :: tail -> 
     if not (head a b = 0) then head a b else 
     lexiCompare tail a b 

let xComparer p1 p2 = 
    if p1.X > p2.X then 1 else 
    if p1.X < p2.X then -1 else 
    0 

let yComparer p1 p2 = 
    if p1.Y > p2.Y then 1 else 
    if p1.Y < p2.Y then -1 else 
    0 

let coordCompare = 
    lexiCompare [ yComparer; xComparer ] 

,让我做

let lowest (points: Point list) = 
    List.sortWith coordCompare points 
    |> List.head 

到目前为止,一切都很好。但是,这感觉有点过分。我必须创建特定的比较器返回-1,0或1,到目前为止,我看不到在List.minBy等情况下直接使用此方法的方法。理想情况下,我想按照提供可以比较的函数列表的方式做一些事情(比如[(fun p - > pX);(fun p - > pY)]),并执行列表中的词典列表min支持该功能列表的项目。

有没有办法在F#中实现这一点?或者我错误地考虑了这个问题?

+2

可能注意:http://stackoverflow.com/a/211691/19299 – Brian 2012-04-07 07:30:46

有没有办法在F#中实现这一点?或者我错误地考虑了这个问题?

> type Point = { X: float; Y: float };; 
type Point = 
    {X: float; 
    Y: float;} 

您可以立即开始比较值:当你定义一个记录类型像你

F#自动为您完成此。例如,定义的点的3元素的列表,并使用内置的List.sort它分类到词典顺序:

> [ { X = 2.0; Y = 3.0 } 
    { X = 2.0; Y = 2.0 } 
    { X = 1.0; Y = 3.0 } ] 
    |> List.sort;; 
val it : Point list = [{X = 1.0; 
         Y = 3.0;}; {X = 2.0; 
            Y = 2.0;}; {X = 2.0; 
               Y = 3.0;}] 

注意,结果通过X第一排序,然后通过Y

您可以使用内置的compare函数比较任何可比类型的两个值。

如果您想使用自定义排序,那么您有两个选项。如果您想使用自定义总订单执行所有操作,那么它属于IComparable和朋友的实现类型定义。如果您想要使用自定义排序进行一些操作,那么您可以使用更高阶的函数,如List.sortByList.sortWith。例如,List.sortBy (fun p -> p.Y, p.X)将按Y排序,然后按X排序,因为F#会为您(!)生成2元组的字典对比。

这是F#的一大优点之一。

+0

谢谢!它看起来像词典分类对超过2个元素的元组进行工作,所以这可以完成这项工作:按照所需的词典顺序将元素列表映射到元组,并将结果排序完美。 – Mathias 2012-04-12 02:36:36

+0

“看起来像词典分类对超过2个元素的元组有影响”。绝对地,'compare'适用于所有可比较的F#类型,包括元组,记录和联合。所有你需要的代码都是为你自动生成的。这非常有用! – 2012-04-12 09:58:49

好了,开始用,你可以依靠内置compare函数F#的:

let xComparer p1 p2 = compare p1.X p2.X 
let yComparer p1 p2 = compare p1.Y p2.Y 

或者,如果你愿意可以清楚地抽象此位:

let compareWith f a b = compare (f a) (f b) 
let xComparer = compareWith (fun p -> p.X) 
let yComparer = compareWith (fun p -> p.Y) 

或者,如您注意到,您可以将此方法直接构建到列表处理函数中:

let rec lexiCompareWith l a b = 
    match l with 
    | [] -> 0 
    | f::fs -> 
     match compare (f a) (f b) with 
     | 0 -> lexiCompareWith fs a b 
     | n -> n 

这里的一个重要限制是,既然你把它们放到一个列表中,这些函数都必须具有相同的返回类型。在Point示例中这不是问题(因为这两个函数的类型都是Point -> float),但它会阻止您按名称排序两个Person对象,然后使用年龄(因为第一个投影的类型为Person -> string,但第二个的类型为Person -> int )。

+0

谢谢,非常有帮助 - 内置的比较已经是有用的信息。你在最后提出的问题是:各种返回类型是相关的,至于我必须处理的另一个例子,我有一个浮动和整数的混合包。 – Mathias 2012-04-07 04:31:09

我不认为我正确理解你的问题,但不会下面的代码工作正常吗?

let lowest (points : Point list) = List.sort points |> List.head 

看来,F#执行记录数据类型的隐式比较。我的小实验表明,比较恰好是词典。但我找不到任何证据支持这一结果。

所以我不确定F#按字典顺序比较记录。我仍然可以用下面的方式用元组来代替:

let lowest (points : Point list) = 
    let tuple = List.map (fun pt -> (pt.X, pt.Y)) points |> List.sort |> List.head 
    { X = fst tuple; Y = snd tuple } 

我希望这篇文章可以帮助。

+0

我的问题不是专门关于点的列表;我所追求的是能够定义要使用的词典顺序。例如,即使上面的排序按X和Y排序,我想要按Y排序,然后X排序 - 我该怎么去做?有趣的是,记录排序虽然默认工作! – Mathias 2012-04-07 19:50:38