在F#中使用布尔函数作为模式鉴别器

asi*_*ahi 7 f# pattern-matching active-pattern

我试着谷歌搜索这个,但我找不到一系列的文字引导我到我想做的事情.

我正在尝试解决Project Euler Problem 54,我有这个相当荒谬的功能:

let evaluate hand =
    if isRoyalFlush hand then 9
    elif isStraightFlush hand then 8
    elif isFour hand then 7
    elif isFullHouse hand then 6
    elif isFlush hand then 5
    elif isStraight hand then 4
    elif isThree hand then 3
    elif isTwoPair hand then 2
    elif isPair hand then 1
    else 0
Run Code Online (Sandbox Code Playgroud)

所有isSomething关键字都是采用string array并返回布尔值的函数.使用模式匹配是否有更优雅的方法?

这不起作用:

match true with
| isRoyalFlush hand -> 9
| isStraightFlush hand -> 8
// .. etc
Run Code Online (Sandbox Code Playgroud)

我正在寻找这样的东西:

match hand with 
| isRoyalFlush -> 9
| isStraightFlush -> 8
// .. etc
Run Code Online (Sandbox Code Playgroud)

我记得曾经在某个时刻看到类似的东西,但我不记得在哪里或如何找到它.

rmu*_*unn 9

你想要活跃的模式:

let (|IsRoyalFlush|_|) hand =
    if isRoyalFlush hand then Some () else None

let (|IsStraightFlush|_|) hand =
    if isStraightFlush hand then Some() else None

// etc.

match hand with
| IsRoyalFlush -> 9
| IsStraightFlush -> 8
// etc.
Run Code Online (Sandbox Code Playgroud)

或者更好的是,将所有公共代码提取到一个简单的活动模式构建器中:

let toActivePattern pred x =
    if pred x then Some () else None

let (|IsRoyalFlush|_|) = isRoyalFlush |> toActivePattern
let (|IsStraightFlush|_|) = isStraightFlush |> toActivePattern
// etc.

match hand with
| IsRoyalFlush -> 9
| IsStraightFlush -> 8
// etc.
Run Code Online (Sandbox Code Playgroud)

如果你不理解第二个例子是如何工作的,请留下评论,我会更深入.

一起组成活动模式

由于活动模式只是函数,因此您可以使用标准函数组合将它们连接在一起.那么,几乎标准的功能组成.使用>>运算符的正常函数组合意味着"获取函数1 的结果,并将其用作函数2的输入".但是在这里,函数1和函数2都返回Some (),但是取一个int; 你不能将1的输出传递给2的输入,因为它们不是兼容的类型.但我们想要的实际上是将相同的输入传递给两个函数,并将它们的输出结合起来.

因此,我们不是使用普通的函数组合,而是定义我们自己的函数,它接受两个活动模式,Some ()如果两个模式都匹配输入,则返回:

let matchesBoth pattern1 pattern2 x =
  match pattern1 x with
  | None -> None
  | Some _ -> pattern2 x
Run Code Online (Sandbox Code Playgroud)

在我们处理它时,让我们定义一个自定义操作符,以便您可以看到它是如何工作的.此matchesBoth函数非常类似于&&运算符,因为Some ()只有当两个模式都Some ()为任何给定输入返回时,它才会返回x.我们不应该重载&&运算符以采用不同的类型,所以让我们创建一个看起来像的自定义运算符&&,但提醒我们它正在组合两个活动模式.如果我们的运营商看起来像|&&|,那应该是完美的.所以让我们创造它:

let (|&&|) = matchesBoth
Run Code Online (Sandbox Code Playgroud)

而已!现在我们可以这样做:

let (|Div3|_|) n =
  if n % 3 = 0 then Some () else None

let (|Div5|_|) n =
  if n % 5 = 0 then Some () else None

let (|Div15|_|) = (|Div3|_|) |&&| (|Div5|_|)

let fizzbuzz n =
    match n with
    | Div15 -> "FizzBuzz"
    | Div5 -> "Buzz"
    | Div3 -> "Fizz"
    | _ -> string n

fizzbuzz 30  // Result: "FizzBuzz"
fizzbuzz 31  // Result: "31"
Run Code Online (Sandbox Code Playgroud)

或者,为您的例子:

let (|IsStraightFlush|_|) = (|IsStraight|_|) |&&| (|IsFlush|_|)
Run Code Online (Sandbox Code Playgroud)


Jus*_*mer 7

活跃模式当然是另一种选择,但我有时会使用函数和值的表:

let handScores = 
  [|
  //Which hand?       Score
    isRoyalFlush    , 9
    isStraightFlush , 8
    isFour          , 7
    isFullHouse     , 6
    isFlush         , 5
    isStraight      , 4
    isThree         , 3
    isTwoPair       , 2
    isPair          , 1
    (fun _ -> true) , 0 // Matches all hands
  |]
let handScore hand = 
  handScores 
  |> Array.pick (fun (t, s) -> if t hand then Some s else None)
Run Code Online (Sandbox Code Playgroud)