Создайте случайное значение из пользовательского типа данных в Haskell
Аналогично: Haskell Random from Datatype
Я создал тип данных, чтобы содержать различные виды оружия в игре Rock Paper Scissor.
data Weapon = Rock | Paper | Scissor
Теперь я хотел бы создать случайное оружие, которое будет использоваться компьютером против пользователя.
Я взглянул на аналогичную ссылку, которую я опубликовал в начале, но для меня это кажется слишком общим.
Я могу генерировать случайные числа из любого другого типа. То, что я могу понять, - это сделать мой тип данных экземпляром класса Random.
Ответы
Ответ 1
Чтобы создать случайный Weapon
, внесите ли вы Weapon
экземпляр Random
или нет, вам нужен способ сопоставления чисел с Weapon
s. Если вы выберете 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
в синхронизации с типом - также выводить 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')
Ответ 2
В то время как подход Дэниела Фишера 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
более вероятно, чем два других. Конечно, вы должны делать такую вещь, только если неравномерное распределение канонически для вашего типа данных, конечно, не в этом примере.
Ответ 3
{-# 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.
Ответ 4
Если у вас есть генератор для данных, вы можете использовать oneof
из Test.QuickCheck.Gen