6

次の形式のタイプを表現したい:

(Card, Suit)

Cardインスタンスがセットに含まれるカードゲームでカードを表すには、次のようにします。

{2, 3, 4, 5, 6, 7, 8, 9, J, Q, K, 1}

セットにSuitインスタンスが含まれます。

{S, D, H, C}

数値がなければ、2つのデータ宣言で処理します。

data Suit = S | D | H | C deri...

しかし、明らかに、これらのnullアリティタイプに数値を追加すると失敗します。

だから私の質問は、Cで見つけた列挙型の種類をどのようにシミュレートするかということです。

型システムの基本的なポイントを誤解していると思いますので、よろしくお願いします!

編集:いくつかのコンテキストを追加します:このオイラー問題に含まれるデータを表現したいのですが、確認できるように、データはスペードのエースの場合は1S、ダイアモンドの2の場合は2Dなどの形式で表されます。 ..

私が本当に望んでいるのは、文字列に対して直接読み取り操作を実行して、対応するオブジェクトを取得できるようにすることです。

4

3 に答える 3

22

私は実際、ポーカーボットを開発していたときから実装が便利でした。特に洗練されているわけではありませんが、機能します。

まず、関連するタイプ。ランクとスーツは列挙型ですが、カードは明らかな複合タイプです(カスタムShowインスタンスを使用)

import Text.ParserCombinators.Parsec

data Suit = Clubs | Diamonds | Hearts | Spades deriving (Eq,Ord,Enum,Show)

data Rank = Two | Three | Four | Five | Six | Seven | Eight | Nine | Ten
          | Jack | Queen | King | Ace deriving (Eq,Ord,Enum,Show)  

data Card = Card { rank :: Rank
                 , suit :: Suit } deriving (Eq,Ord,Bounded)

instance Show Card where
    show (Card rank suit) = show rank ++ " of " ++ show suit

次に、Parsecを使用する解析コードがあります。これを開発して、より洗練されたものにしたり、より適切なエラーメッセージを返したりすることができます。

Matveyがコメントで述べたように、プログラム内の表現に文字列を解析する問題は、列挙型の表現方法と直交している(またはそうであるべきである)ことに注意してください。ここで私は直交性をだまして壊しました:ランクを並べ替えたい場合(たとえば、Aceランクを下にする場合)、パーサーは存在、存在などTwoの内部表現に依存するため、構文解析コードを壊します。Two0Three1

より良いアプローチは、すべてのランクを明示的に綴ることですparseRank(これは私が元のコードで行っていることです)。このように書いたのは、(a)スペースを節約し、(b)原則として数値をランクに解析する方法を示し、(c)明示的に記述された悪い習慣の例を示して、回避できるようにするためです。将来。

parseSuit :: Parser Suit
parseSuit = do s <- oneOf "SDCH"
               return $ case s of
                'S' -> Spades
                'D' -> Diamonds
                'H' -> Hearts
                'C' -> Clubs

parseRank :: Parser Rank
parseRank = do r <- oneOf "23456789TJQKA"
               return $ case r of
                'T' -> Ten
                'J' -> Jack
                'Q' -> Queen
                'K' -> King
                'A' -> Ace
                 n  -> toEnum (read [n] - 2)

parseCard :: Parser Card
parseCard = do r <- parseRank
               s <- parseSuit
               return $ Card { rank = r, suit = s }

readCard :: String -> Either ParseError Card
readCard str = parse parseCard "" str

そして、ここでそれが実行されています:

*Cards> readCard "2C"
Right Two of Clubs
*Cards> readCard "JH"
Right Jack of Hearts
*Cards> readCard "AS"
Right Ace of Spades

編集:

@ yatima2975はコメントで、で遊んで楽しむことができるかもしれないと述べていますOverloadedStrings。私はそれを多くの有用なことをするようにさせることができませんでした、しかしそれは有望であるように思われます。{-# LANGUAGE OverloadedStrings #-}まず、ファイルの先頭に配置して言語オプションを有効にしimport GHC.Exts ( IsString(..) )、関連する型クラスをインポートする行を含める必要があります。Card次に、を文字列リテラルにすることができます。

instance IsString Card where
    fromString str = case readCard str of Right c -> c

これにより、タイプを明示的に書き出すのではなく、カードの文字列表現でパターンマッチングを行うことができます。

isAce :: Card -> Bool
isAce "AH" = True
isAce "AC" = True
isAce "AD" = True
isAce "AS" = True
isAce _    = False

関数への入力として文字列リテラルを使用することもできます。

printAces = do
    let cards = ["2H", "JH", "AH"]
    mapM_ (\x -> putStrLn $ show x ++ ": " ++ show (isAce x)) cards

そして、ここでそれが実行されています:

*Cards> printAces
Two of Hearts: False
Jack of Hearts: False
Ace of Hearts: True
于 2012-04-25T12:45:27.080 に答える
9
data Card = Two | Three | Four | Five | Six
          | Seven | Eight | Nine | Ten
          | Jack | Queen | King | Ace
    deriving Enum

型クラスを実装するということは、とを使用してとの間で変換Enumできることを意味します。fromEnumtoEnumCardInt

ただし、それが重要な場合fromEnum Twoは、のインスタンスを手動で2実装する必要があります。(自動派生インスタンスは、Cと同じようにで始まりますが、すべてを自分で行わずにオーバーライドする方法はありません。)EnumCard0

nb必要ないかもしれません---必要なのがsのようなEnum演算子を使用することだけである場合は、を使用する必要があります。<==Cardderiving Ord


編集:

文字列が次のように見えることを期待するため、フォームまたはをに変換するために使用readすることはできません(たとえば、最初に要求したフォーム、または上記で提案したフォーム)。String"2S""QH"(Card, Suit)read"(a,b)""(2,S)""(Two,S)"

文字列を自分で解析する関数を作成する必要があります。パーセク(ParsecやAttoparsecなど)を使用することもできますが、この場合は、手で書くのに十分簡単なはずです。

例えば

{-# LANGUAGE TupleSections #-}

parseSuit :: String -> Maybe Suit
parseSuit "S" = Just S
...
parseSuit _   = Nothing

parseCard :: String -> Maybe (Card, Suit)
parseCard ('2' : s) = fmap (Two,) (parseSuit s)
...
parseCard _         = Nothing
于 2012-04-25T10:03:13.583 に答える
2

数字の前に文字、さらには単語を付けます。また、1文字の略語をあまり多く使用しません。<code>HKなどはまったく読めません。

data Suit = Club | Spade | Heart | Diamond
data Card = Card1 | Card2 | … | Jack | Queen | King | Ace

…しかし、代わりに値に数字の単語( One、 )を使用するというデイブの提案を好む。Two

于 2012-04-25T10:03:47.937 に答える