2012-08-04 12 views
10

similares: Haskell Random from DatatypeGenerar un valor aleatorio a partir de un tipo de datos definido por el usuario en Haskell

He creado un tipo de datos que contienen las diferentes armas en el juego Rock Paper Scissor.

data Weapon = Rock | Paper | Scissor 

Ahora me gustaría generar un arma aleatoria que será utilizada por la computadora contra el usuario. He echado un vistazo al enlace similar que publiqué al principio, pero parece demasiado general para mí.

Soy capaz de generar números aleatorios de cualquier otro tipo. Lo que sí puedo entender es cómo hacer que mi tipo de datos sea una instancia de la clase Aleatoria.

+1

[¿Qué has intentado?] (Http://www.whathaveyoutried.com) –

Respuesta

14

Para generar una Weapon al azar, si usted hace Weapon una instancia de Random o no, lo que necesita es una manera de asignar números a Weapon s. Si deriva Enum para el tipo, el compilador define un mapa ay desde Int s. Por lo tanto, podría definir

randomWeapon :: RandomGen g => g -> (Weapon, g) 
randomWeapon g = case randomR (0,2) g of 
        (r, g') -> (toEnum r, g') 

por ejemplo. Con una instancia Enum, también se puede hacer fácilmente Weapon una instancia de 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') 

Si existe la posibilidad de añadir o retirar constructores del tipo, la mejor manera de mantener los límites de randomR en sincronía con el tipo de modo que también derivan Bounded, como Joachim Breitnerimmediately 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') 
+6

con un adicional derivada acotada Por ejemplo, puede hacer que el código sea completamente abstracto. –

+0

¿Qué sucede si agrego una nueva arma?¿Existe una forma general de saber la cantidad de valores diferentes que puedo tener para el tipo de datos? – Oni

+0

Luego, derivar 'Limitado' es la forma más sencilla de mantener las cosas sincronizadas. Actualización de respuesta. –

8

Mientras que el enfoque de Daniel Fischer Enum es sin duda una buena manera de hacer esto en general, no es realmente necesario para utilizar un mapeo explícito de Int s. Puede así hacer precisamente

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

utilizando la instancia de DoubleRandom. Esto es menos eficiente que con un derivado Enum ejemplo, pero más flexible - por ejemplo, se podría definir fácilmente una distribución no igual

random g = case random g of 
       (r,g') | r < 1/4 = (Rock , g') 
         | r < 1/2 = (Paper , g') 
         | otherwise = (Scissors, g') 

donde es más probable que los otros dos Scissors. Por supuesto, solo debe hacer tal cosa si la distribución no igual es de alguna manera canónica para su tipo de datos, ciertamente no en este ejemplo.

+0

Esto es muy agradable y legible. –

+0

['randomR'] (https://hackage.haskell.org/package/random-1.1/docs/System-Random.html#v:randomR) dice que espera una distribución uniforme entre los posibles resultados. Para una distribución no uniforme, probablemente sea mejor implementar una función separada para evitar romper ese contrato de clase de clase. – 4castle

+0

@ 4castle ese tipo de contrato de clase realmente no se sostiene de todos modos. Por ej. 'Double'," uniform "se entiende en el sentido de una distribución continua; es decir, la _probability density_ en la línea real es constante. Tiene sentido seguro. Sin embargo, la línea real no es muestreada uniformemente por valores de coma flotante, por lo tanto, si realmente observara la distribución de los valores individuales "binarios" individuales binarios, notará que '0.8346253987632499' ocurre mucho más a menudo que' 0.0000003265368256732462 '. – leftaroundabout

5
{-# 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]) 

Ahora usted puede decir:

r <- randomIO :: Anything 

donde todo debe ser una instancia de las clases de enumeración y limitada.

+4

¿Cuál es la razón para definir 'BoundedEnum' en lugar de simplemente usar' (Bounded a, Enum a) => Random a'? – huon

2

Si usted tiene un generador de los datos se puede utilizar oneof de Test.QuickCheck.Gen

Cuestiones relacionadas