14

类似:来自数据类型的 Haskell Random

我创建了一个数据类型来包含石头剪刀布游戏中的不同武器。

data Weapon = Rock | Paper | Scissor

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

我能够从任何其他类型生成随机数。我能想到的是如何使我的数据类型成为 Random 类的实例。

4

5 回答 5

16

要生成 random Weapon,无论您是否创建Weapon实例Random,您都需要一种将数字映射到Weapons 的方法。如果您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')
于 2012-08-04T20:32:17.440 回答
8

虽然 Daniel Fischer 的Enum方法确实是一种很好的通用方法,但实际上没有必要使用来自Ints 的显式映射。你也可以这样做

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比其他两个更有可能。当然,只有在不等分布在某种程度上对您的数据类型而言是规范的情况下,您才应该这样做,在这个示例中当然不是。

于 2012-08-05T10:31:00.757 回答
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

其中任何东西都必须是 Enum 和 Bounded 类的实例。

于 2012-08-05T13:13:23.520 回答
2

如果您有数据生成器,则可以使用oneofTest.QuickCheck.Gen

于 2012-08-06T08:56:19.047 回答
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

于 2019-01-04T18:39:08.697 回答