【问题标题】:Using boolean functions as pattern discriminators in F#在 F# 中使用布尔函数作为模式鉴别器
【发布时间】:2016-09-01 03:02:55
【问题描述】:

我尝试用谷歌搜索这个,但我找不到引导我去做我想做的事情的词集。

我正在尝试解决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 并返回布尔值的函数。有没有更优雅的方式使用模式匹配来做到这一点?

这不起作用:

match true with
| isRoyalFlush hand -> 9
| isStraightFlush hand -> 8
// .. etc

我正在寻找这样的东西:

match hand with 
| isRoyalFlush -> 9
| isStraightFlush -> 8
// .. etc

我记得有一次看到过类似的东西,但我不记得在哪里或如何找到它。

【问题讨论】:

  • 主动模式就是你想要的
  • 主动模式方法看起来稍微好一些,但它上面需要很多样板。我更喜欢@FuleSnabel 的答案,它只使用高阶函数和数据。但实际上,我认为你的代码很好,而且完全可以理解。添加一些列对齐可能会降低它在未来被意外破坏的可能性。
  • 我也认为OP发布的代码没有什么特别的问题。

标签: f# pattern-matching active-pattern


【解决方案1】:

你想要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.

如果您不了解第二个示例的工作原理,请发表评论,我会更深入地介绍。

将活动模式组合在一起

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

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

let matchesBoth pattern1 pattern2 x =
  match pattern1 x with
  | None -> None
  | Some _ -> pattern2 x

在此过程中,让我们定义一个自定义运算符,以便您了解它是如何工作的。这个matchesBoth 函数的工作方式与&& 运算符非常相似,只有当两种 模式对于任何给定的输入x 都返回Some () 时,它才会返回Some ()。我们不应该重载&& 运算符来采用不同的类型,所以让我们创建一个看起来像&& 的自定义运算符,但提醒我们它结合了两个活动模式。如果我们的运算符看起来像|&&|,那应该是完美的。所以让我们创建它:

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|_|)

【讨论】:

  • 谢谢,我也很怀疑。不过,有点切题的问题:我尝试将我的谓词重写为活动模式(返回 Some 和 None),但后来我不知道是否可以将两个活动模式组合在一起。如果没有 hacky 代码,这样的事情可能吗?
  • 换句话说,还有其他方法吗:let (|IsStraightFlush|_|) hand = if ((|IsFlush|_|) hand |> Option.isSome) && ((|IsStraight|_|) hand |> Option.isSome) then Some () else None ?
  • @asibahi - 我已经编辑了我的答案来回答你关于结合两个活动模式的第二个问题。
【解决方案2】:

主动模式当然是一种替代方案,但我有时会使用函数和值表:

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)

【讨论】:

    猜你喜欢
    • 2012-10-15
    • 2014-10-27
    • 1970-01-01
    • 2010-12-21
    • 2021-01-23
    • 2014-07-02
    • 2016-12-12
    • 2017-03-17
    • 1970-01-01
    相关资源
    最近更新 更多