在F#的许多领域,我还是个新手。我问这个问题更多的是出于好奇,而不是出于实际的商业需要。有没有任何方法可以匹配列表中的第一个n项,而不管它们的出现顺序如何?为了澄清,请考虑以下示例:
type MyEnum =
| Foo of int
| Bar of float
| Baz of string
let list = [ Foo 1 ; Bar 1.0 ; Baz "1" ]
现在,假设我想调用some_func
,如果列表中的前两个项目按任何顺序是Foo
和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项按任何顺序是Foo
、Bar
和Baz
,那么如果我需要调用函数,该怎么办?使用上面相同的技术需要我编写所有6个不同的排列(或n!用于n项)。理想情况下,我希望能够做一些类似这样的事情:
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"
有没有什么方法可以用某种主动模式来实现这一点,而不必对不同的排列进行硬编码?
这里有一个解决问题的尝试。它使用位图对每个并集案例进行评分,并检查分数之和是否为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
非常有趣的案例。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的顺序,并且忘记在列表中对其进行重新排序,则它将永远不会匹配。
如果你想走这种通用的方式,我的建议是:创建一个单元测试来断言匹配总是有效的。