在 F# 中使用布尔函数作为模式鉴别器
Using boolean functions as pattern discriminators in F#
我试着用谷歌搜索这个,但我找不到一组引导我去做我想做的事情的词。
我正在尝试解决 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
所有 isSomething
关键字都是采用 string array
和 return 布尔值的函数。有没有使用模式匹配的更优雅的方法?
这行不通:
match true with
| isRoyalFlush hand -> 9
| isStraightFlush hand -> 8
// .. etc
我正在寻找这样的东西:
match hand with
| isRoyalFlush -> 9
| isStraightFlush -> 8
// .. etc
我记得曾经见过类似的东西,但我不记得在哪里或如何找到它。
你想要active patterns:
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.
或者更好的是,将所有通用代码提取到一个简单的活动模式构建器中:
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.
如果您不明白第二个示例的工作原理,请发表评论,我将深入探讨。
一起组合活动模式
由于活动模式只是函数,您可以使用标准函数组合将它们连接在一起。好吧,几乎标准函数组合。使用 >>
运算符的正常函数组合表示 "take the result of function 1, and use it as the input to function 2"。但是这里,函数1和函数2都是returnSome ()
,但是取一个int;您不能将 1 的输出传递给 2 的输入,因为它们不是兼容的类型。但实际上我们想要的是将相同的输入传递给两个函数,并合并它们的输出。
因此,我们将定义我们自己的函数,而不是使用正常的函数组合,它采用两个活动模式,并且 returns Some ()
如果两个模式都匹配输入:
let matchesBoth pattern1 pattern2 x =
match pattern1 x with
| None -> None
| Some _ -> pattern2 x
在此期间,让我们定义一个自定义运算符,以便您了解其工作原理。此 matchesBoth
函数的工作方式与 &&
运算符非常相似,因为只有当 both 模式 return Some ()
对于任何给定的输入 x
。我们不应该重载 &&
运算符来采用不同的类型,所以让我们创建一个看起来像 &&
的自定义运算符,但提醒我们它正在组合两个活动模式。如果我们的运算符看起来像 |&&|
,那应该是完美的。让我们创建它:
let (|&&|) = matchesBoth
就是这样!现在我们可以这样做:
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"
或者,对于您的示例:
let (|IsStraightFlush|_|) = (|IsStraight|_|) |&&| (|IsFlush|_|)
活动模式当然是另一种选择,但我有时会使用 table 函数和值:
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)
我试着用谷歌搜索这个,但我找不到一组引导我去做我想做的事情的词。
我正在尝试解决 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
所有 isSomething
关键字都是采用 string array
和 return 布尔值的函数。有没有使用模式匹配的更优雅的方法?
这行不通:
match true with
| isRoyalFlush hand -> 9
| isStraightFlush hand -> 8
// .. etc
我正在寻找这样的东西:
match hand with
| isRoyalFlush -> 9
| isStraightFlush -> 8
// .. etc
我记得曾经见过类似的东西,但我不记得在哪里或如何找到它。
你想要active patterns:
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.
或者更好的是,将所有通用代码提取到一个简单的活动模式构建器中:
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.
如果您不明白第二个示例的工作原理,请发表评论,我将深入探讨。
一起组合活动模式
由于活动模式只是函数,您可以使用标准函数组合将它们连接在一起。好吧,几乎标准函数组合。使用 >>
运算符的正常函数组合表示 "take the result of function 1, and use it as the input to function 2"。但是这里,函数1和函数2都是returnSome ()
,但是取一个int;您不能将 1 的输出传递给 2 的输入,因为它们不是兼容的类型。但实际上我们想要的是将相同的输入传递给两个函数,并合并它们的输出。
因此,我们将定义我们自己的函数,而不是使用正常的函数组合,它采用两个活动模式,并且 returns Some ()
如果两个模式都匹配输入:
let matchesBoth pattern1 pattern2 x =
match pattern1 x with
| None -> None
| Some _ -> pattern2 x
在此期间,让我们定义一个自定义运算符,以便您了解其工作原理。此 matchesBoth
函数的工作方式与 &&
运算符非常相似,因为只有当 both 模式 return Some ()
对于任何给定的输入 x
。我们不应该重载 &&
运算符来采用不同的类型,所以让我们创建一个看起来像 &&
的自定义运算符,但提醒我们它正在组合两个活动模式。如果我们的运算符看起来像 |&&|
,那应该是完美的。让我们创建它:
let (|&&|) = matchesBoth
就是这样!现在我们可以这样做:
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"
或者,对于您的示例:
let (|IsStraightFlush|_|) = (|IsStraight|_|) |&&| (|IsFlush|_|)
活动模式当然是另一种选择,但我有时会使用 table 函数和值:
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)