4

当谈到 F# 的许多领域时,我仍然是新手。我问这个问题更多是出于好奇,而不是出于实际的业务需求。有什么方法可以匹配列表中的前n 个项目,而不管它们出现的顺序是什么?为了澄清,请考虑以下示例:

type MyEnum = 
    | Foo of int
    | Bar of float
    | Baz of string
let list = [ Foo 1 ; Bar 1.0 ; Baz "1" ]

some_func现在,假设如果列表中的前两项是 aFoo和 a ,我想以Bar任意顺序调用。匹配两种可能的排列相当容易:

let result = 
    match list with
    | Foo n :: Bar x :: _ 
    | Bar x :: Foo n :: _ -> some_func n x
    | _ -> failwith "First 2 items must be Foo and Bar"

但是,如果前 3 项是 a并且以任何顺序调用函数Foo,我该怎么办?使用上述相同的技术将需要我编写所有 6 种不同的排列(或n!对于n 个项目)。理想情况下,我希望能够按照以下方式做一些事情:BarBaz

let result = 
    match list with
    | (AnyOrder [ Foo n ; Bar x ; Baz s ]) :: _ -> some_func n x s
    | _ -> failwith "First 3 items must be Foo, Bar, and Baz"

有没有办法通过某种活动模式来实现这一点,而不必对不同的排列进行硬编码?

4

2 回答 2

5

这是解决问题的一种尝试。它使用位图对每个联合案例进行评分,并检查分数的总和是否为 7:

let (|AnyOrder|_|) s =
    let score = function | Foo _ -> 1 | Bar _ -> 2 | Baz _ -> 4
    let firstElementsWithScores =
        s
        |> Seq.truncate 3
        |> Seq.map (fun x -> x, score x)
        |> Seq.sortBy (fun (_, x) -> x)
        |> Seq.toList
    let sumOfScores =
        firstElementsWithScores |> List.sumBy (fun (_, x) -> x)
    if sumOfScores = 7
    then
        match firstElementsWithScores |> List.map fst with
        | [Foo x ; Bar y ; Baz z ] -> Some (x, y, z)
        | _ -> None
    else None

如果得分为 7,它会截断输入列表并对其进行排序,然后对截断的得分列表使用模式匹配以创建匹配元素的元组。

这是使用它的一种方法:

let checkMatches s =
    match s with
    | AnyOrder (x, y, z) -> [Foo x; Bar y; Baz z]
    | _ -> []

FSI的一些例子:

> checkMatches list;;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "1"]
> checkMatches [Foo 1; Bar 1.0; Foo 2];;
val it : MyEnum list = []
> checkMatches [Foo 1; Bar 1.0; Baz "1"; Foo 2];;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "1"]
> checkMatches [Foo 1; Bar 1.0; Bar 2.0; Foo 2];;
val it : MyEnum list = []
> checkMatches [Bar 1.0; Foo 1; Baz "2.0"; Foo 2];;
val it : MyEnum list = [Foo 1; Bar 1.0; Baz "2.0"]

AnyOrder是带有签名的部分活动模式

seq<MyEnum> -> (int * float * string) option
于 2014-11-06T08:08:48.397 回答
4

非常有趣的案例。Mark 提出的解决方案效果很好,但缺点是该功能不可重用,我的意思是该 DU 非常具体。

这是一个通用的解决方案,但这里的缺点是您需要按照创建 DU 的顺序指定列表。

let (|AnyOrderOfFirst|) n = Seq.take n >> Seq.sort >> Seq.toList

let result = 
    match list with
    | AnyOrderOfFirst 3 [ Foo n ; Bar x ; Baz s ] -> some_func n x s
    | _ -> failwith "First 3 items must be Foo, Bar, and Baz"

如果您更改 DU 更改 TAG 的顺序并忘记在列表中重新排序它们,它将永远不会匹配。

如果您想采用这种通用方式,我的建议是:创建一个单元测试来断言匹配始终有效。

于 2014-11-06T09:36:37.933 回答