8

これをグーグルで調べてみましたが、私がやろうとしていることを導く言葉のコレクションを見つけることができませんでした.

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

ある時点でそのようなものを見たのを覚えていますが、どこでどのように見つけたのか思い出せません。

4

2 に答える 2

11

アクティブなパターンが必要です:

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.

2 番目の例の仕組みがわからない場合は、コメントを残してください。詳しく説明します。

アクティブなパターンを一緒に構成する

アクティブなパターンは単なる関数であるため、標準的な関数構成を使用してそれらを結合できます。まあ、ほぼ標準的な機能構成。>>演算子を使用した通常の関数合成は、 「関数 1 の結果を関数 2 の入力として使用する」ことを意味します。ただし、ここでは、関数 1 と関数 2 の両方が を返しSome ()ますが、int を取ります。互換性のある型ではないため、1 の出力を 2 の入力に渡すことはできません。しかし、実際には両方の関数に同じ入力を渡し、それらの出力を結合する必要があります。

Some ()したがって、通常の関数構成を使用する代わりに、2 つのアクティブなパターンを受け取り、両方のパターンが入力に一致した場合に戻る独自の関数を定義します。

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

では、カスタム オペレータを定義して、それがどのように機能するかを見てみましょう。この関数は、指定された入力に対して両方のパターンが返される場合にのみ返されるという点で、演算子とmatchesBoth非常によく似ています。オペレータをオーバーロードして別の型を取るべきではないので、 のようなカスタム オペレータを作成しましょう。ただし、2 つのアクティブなパターンを結合していることを思い出してください。演算子が のように見える場合、それは完璧なはずです。それでは、それを作成しましょう:&&Some ()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|_|)
于 2016-09-01T05:00:56.937 に答える
8

アクティブなパターンは確かに代替手段ですが、関数と値のテーブルを使用することがあります。

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)
于 2016-09-01T06:30:41.803 に答える