2014-11-06 25 views
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如果在列表中的前两项是FooBar,以任意顺序。这是很容易只匹配两个可能的排列:

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项是FooBar以任意顺序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" 

有没有办法用某种形式的active pattern做到这一点,而无需进行硬编码的不同排列?

回答

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改变标签的顺序,忘记也重新排序列表中它永远不会匹配。

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

+0

不错。我喜欢非常简单的方法。唯一有点恼人的是,匹配表达式*中的列表有*要排序 - 'AnyOrderOfFirst 3 [Bar x; Foo n;巴兹]'永远不会匹配。我也喜欢你可以做一些事情,比如'前4个中的2个是'Foo''s'和'AnyOrderOfFirst 4 [Foo a; Foo b; _; _]',但要用'Bar'做同样的事情,你必须写出3种不同的情况。 – 2014-11-06 13:20:15

+0

是的,这是这个解决方案的缺点:匹配中的列表务必排序,排序顺序是在DU中声明TAG的顺序。这应该始终同步。 – Gustavo 2014-11-06 13:26:55

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 
相关问题