我创建了一个数据类型来包含石头剪刀布游戏中的不同武器。
data Weapon = Rock | Paper | Scissor
现在我想生成一个随机武器,计算机将使用它来对付用户。我看了一下我在开始时发布的类似链接,但这对我来说似乎太笼统了。
我能够从任何其他类型生成随机数。我能想到的是如何使我的数据类型成为 Random 类的实例。
我创建了一个数据类型来包含石头剪刀布游戏中的不同武器。
data Weapon = Rock | Paper | Scissor
现在我想生成一个随机武器,计算机将使用它来对付用户。我看了一下我在开始时发布的类似链接,但这对我来说似乎太笼统了。
我能够从任何其他类型生成随机数。我能想到的是如何使我的数据类型成为 Random 类的实例。
要生成 random Weapon
,无论您是否创建Weapon
实例Random
,您都需要一种将数字映射到Weapon
s 的方法。如果您Enum
为该类型派生,Int
则编译器定义与 s 的映射。所以你可以定义
randomWeapon :: RandomGen g => g -> (Weapon, g)
randomWeapon g = case randomR (0,2) g of
(r, g') -> (toEnum r, g')
例如。使用Enum
实例,您还可以轻松创建Weapon
以下实例Random
:
instance Random Weapon where
random g = case randomR (0,2) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
如果有可能从类型中添加或删除构造函数,那么保持边界randomR
与类型同步的最佳方法是也派生Bounded
,正如Joachim Breitner 立即建议的那样:
data Weapon
= Rock
| Paper
| Scissors
deriving (Bounded, Enum)
instance Random Weapon where
random g = case randomR (fromEnum (minBound :: Weapon), fromEnum (maxBound :: Weapon)) g of
(r, g') -> (toEnum r, g')
randomR (a,b) g = case randomR (fromEnum a, fromEnum b) g of
(r, g') -> (toEnum r, g')
虽然 Daniel Fischer 的Enum
方法确实是一种很好的通用方法,但实际上没有必要使用来自Int
s 的显式映射。你也可以这样做
instance Random Weapon where
random g = case random g of
(r,g') | r < 1/3 = (Rock , g')
| r < 2/3 = (Paper , g')
| otherwise = (Scissors, g')
使用. Double
_ Random
这比派生Enum
实例效率低,但更灵活——例如,您可以轻松定义不等分布
random g = case random g of
(r,g') | r < 1/4 = (Rock , g')
| r < 1/2 = (Paper , g')
| otherwise = (Scissors, g')
哪里Scissors
比其他两个更有可能。当然,只有在不等分布在某种程度上对您的数据类型而言是规范的情况下,您才应该这样做,在这个示例中当然不是。
{-# LANGUAGE FlexibleInstances, UndecidableInstances,
ScopedTypeVariables, OverlappingInstances #-}
import System.Random
class (Bounded a, Enum a) => BoundedEnum a
instance (Bounded a, Enum a) => BoundedEnum a
instance BoundedEnum a => Random a where
random gen = randomR (minBound :: a, maxBound :: a) gen
randomR (f, t) gen =
(toEnum r :: a, nextGen)
where
(rnd, nextGen) = next gen
r = fromEnum f + (rnd `mod` length [f..t])
现在你可以说:
r <- randomIO :: Anything
其中任何东西都必须是 Enum 和 Bounded 类的实例。
如果您有数据生成器,则可以使用oneof
Test.QuickCheck.Gen
一位同事向我建议了 Alekseev 的解决方案的替代方案,我发现它更具可读性(我仍在学习 Haskell,因此它可能更多地取决于读者而不是代码)。
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
instance {-# OVERLAPPABLE #-} (Bounded a, Enum a) => Random a where
random = randomR (minBound, maxBound)
randomR (f, t) gen =
let (rndInt, nxtGen) = randomR (fromEnum f, fromEnum t) gen
in (toEnum rndInt, nxtGen)
data Weapon = Rock | Paper | Scissor deriving (Eq, Show, Ord, Bounded, Enum)
然后你可以这样做:getRandomWeapon = randomRIO (minBound, maxBound) :: IO Weapon
然后用weaponChoice <- getRandomWeapon