【问题标题】:Lexicographic sorting in F#F#中的字典排序
【发布时间】:2012-04-20 12:44:24
【问题描述】:

我正在玩一个玩具问题(凸面船体识别),并且已经需要字典排序两次。其中一个案例给出了type Point = { X: float; Y: float }的列表,我想按X坐标排序,如果相等,按Y坐标排序。
我最终写了以下内容:

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)]),并做一些类似列表的字典最小的事情支持该功能列表的项目。

有没有办法在 F# 中实现这一点?还是我想错了?

【问题讨论】:

标签: f#


【解决方案1】:

有没有办法在 F# 中实现这一点?还是我想错了?

当您定义像您这样的记录类型时,F# 会自动为您执行此操作:

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

您可以立即开始比较值。例如,定义一个 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 -&gt; p.Y, p.X) 将按 Y 排序,然后按 X 排序,因为 F# 会为您生成 2 元组的字典比较 (!)。

这是 F# 的一大优势。

【讨论】:

  • 谢谢!看起来字典排序适用于超过 2 个元素的元组,所以这可以完成工作:将列表的元素映射到所需字典顺序的元组,并对结果进行排序非常有效。
  • “看起来字典排序适用于超过 2 个元素的元组”。当然,compare 适用于所有可比较的 F# 类型,包括元组、记录和联合。您需要的所有代码都是为您自动生成的。这非常有用!
【解决方案2】:

好吧,首先,你可以依赖 F# 的内置 compare 函数:

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 -&gt; float),但它会阻止您按名称和年龄对两个 Person 对象进行排序(因为第一个投影的类型为 @987654328 @ 但第二个的类型为 Person -&gt; int)。

【讨论】:

  • 谢谢,非常有帮助 - 内置的比较已经是有用的信息。你最后提出的问题是:各种返回类型是相关的,至于我必须处理的另一个例子,我有一个混合的浮点数和整数。
【解决方案3】:

我认为我没有正确理解您的问题,但是下面的代码不能正常工作吗?

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

似乎 F# 对记录数据类型执行隐式比较。我的小实验表明比较恰好是字典顺序的。但是我找不到任何证据来支持这个结果。

所以我还不确定 F# 按字典顺序比较记录。我仍然可以使用 tuple 以以下方式编写:

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 }

希望这篇文章能帮到你。

【讨论】:

  • 我的问题不在于积分列表;我所追求的是能够定义要使用的词典顺序。例如,即使上面的排序按字典顺序按 X 和 Y 排序,想象我想按 Y 和 X 排序 - 我该怎么做呢?有趣的是,记录排序默认工作!
猜你喜欢
  • 1970-01-01
  • 2011-05-18
  • 2013-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-06
  • 2012-06-16
  • 1970-01-01
相关资源
最近更新 更多