从Haskell中的用户定义数据类型生成随机值

时间:2012-08-04 19:42:15

标签: haskell types random

类似:Haskell Random from Datatype

我创建了一种数据类型,用于在Rock Paper Scissor游戏中包含不同的武器。

data Weapon = Rock | Paper | Scissor

现在我想生成一个随机武器,计算机将对用户使用该武器。 我看一下我在开头发布的类似链接,但对我来说似乎太笼统了。

我可以从任何其他类型生成随机数。我可以理解的是如何使我的数据类型成为Random类的一个实例。

5 个答案:

答案 0 :(得分:14)

要生成随机Weapon,无论您是否Weapon Random的实例,您需要的是将数字映射到Weapon的方法。如果为类型派生Enum,则编译器会定义往返Int的映射。所以你可以定义

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与该类型同步的边界的最佳方法也是BoundedJoachim Breitner } immediately suggested

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

答案 1 :(得分:8)

虽然Daniel Fischer的Enum方法确实是一种很好的通用方法,但使用Int的显式映射并不是真的。你也可以这样做

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的{​​{1}}实例。这比派生的Random实例效率低,但更灵活 - 例如,您可以轻松定义不等分布

Enum

其中 random g = case random g of (r,g') | r < 1/4 = (Rock , g') | r < 1/2 = (Paper , g') | otherwise = (Scissors, g') 比其他两个更有可能。当然,如果不等分布在某种程度上对您的数据类型是规范的,那么您应该只做这样的事情,当然不是在这个例子中。

答案 2 :(得分:8)

{-# 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

其中Anything必须是Enum和Bounded类的实例。

答案 3 :(得分:2)

如果您有数据生成器,那么您可以使用Test.QuickCheck.Gen中的oneof

答案 4 :(得分:0)

一位同事向我建议了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提取一个