web-dev-qa-db-fra.com

Générer un entier aléatoire dans une plage de Haskell sans graine

Comment puis-je générer un nombre aléatoire dans Haskell à partir d'une plage (a, b) sans utiliser de graine? 

La fonction doit renvoyer un Int et non un IO Int. J'ai une fonction X qui prend et Int et d'autres arguments et génère quelque chose qui n'est pas un IO.

Si ce n'est pas possible, comment puis-je générer une graine à l'aide de la bibliothèque Time et générer un nombre aléatoire dans la plage avec mkStdGen?

Toute aide sera grandement appréciée.

17
user1054204

Une fonction ne peut pas renvoyer une Int sans IO, sauf s’il s’agit d’une fonction pure, c’est-à-dire que si vous donnez la même entrée, vous obtiendrez toujours la même sortie. Cela signifie que si vous voulez un nombre aléatoire sans IO, vous devrez utiliser une graine comme argument.

  • Si vous choisissez de prendre une graine, celle-ci doit être de type StdGen et vous pouvez utiliser randomR pour en générer un nombre. Utilisez newStdGen pour créer une nouvelle graine (cela devra être fait dans IO).

    > import System.Random
    > g <- newStdGen
    > randomR (1, 10) g
    (1,1012529354 2147442707)
    

    Le résultat de randomR est un tuple dont le premier élément est la valeur aléatoire et le second une nouvelle valeur de départ à utiliser pour générer plus de valeurs.

  • Sinon, vous pouvez utiliser randomRIO pour obtenir un nombre aléatoire directement dans la monade IO, avec tous les éléments StdGen pris en charge pour vous:

    > import System.Random
    > randomRIO (1, 10)
    6
    
38
hammar

Sans recourir à toutes sortes de pratiques non sécuritaires, il est impossible pour une telle fonction d'avoir le type Int plutôt que le type IO Int ou quelque chose de similaire. Les fonctions (ou, dans ce cas, les constantes) de type Int sont pures, ce qui signifie que chaque fois que vous "appelez" la fonction (récupérez la valeur de la constante), vous êtes assuré d'obtenir la même valeur "renvoyée".

Si vous souhaitez qu'une valeur différente, choisie au hasard, soit renvoyée à chaque appel, vous devez utiliser la variable IO- monad.

Dans certains cas, vous souhaiterez peut-être avoir une valeur unique générée aléatoirement pour l’ensemble du programme, c’est-à-dire une valeur qui, du point de vue du programme, se comporte comme s’il s’agissait d’une valeur pure. Chaque fois que vous interrogez la valeur dans la même exécution du programme, vous obtenez la même valeur. Comme le programme dans son ensemble est essentiellement une IO- action, vous pouvez alors générer cette valeur une fois et la transmettre, mais cela peut sembler un peu maladroit. On pourrait soutenir que, dans cette situation, il est toujours prudent d'associer la valeur à une constante de niveau supérieur de type Int et d'utiliser unsafePerformIO pour construire cette constante:

import System.IO.Unsafe  -- be careful!                                         
import System.Random

-- a randomly chosen, program-scoped constant from the range [0 .. 9]            
c :: Int
c = unsafePerformIO (getStdRandom (randomR (0, 9)))
4
Stefan Holdermans
fmap yourFunctionX $ randomRIO (a, b)

ou

fmap (\x -> yourFunctionX aParam x anotherParam) $ randomRIO (a, b)

Le résultat sera alors de type IO whateverYourFunctionXReturns.

Si vous import Control.Applicative, vous pouvez dire

yourFunctionX <$> randomRIO (a, b)

ou

(\x -> yourFunctionX aParam x anotherParam) <$> randomRIO (a, b)

que vous pouvez trouver plus clair

2
dave4420

Notez que vous pouvez obtenir une liste infinie de valeurs aléatoires en utilisant le monad IO et utiliser ce [Int] dans des fonctions non-IO. De cette façon, vous n'avez pas à emporter la graine avec vous, mais vous devez quand même conserver la liste. Heureusement, il existe de nombreuses fonctions de traitement de liste pour simplifier ce type de thread, et vous pouvez toujours utiliser le monad State dans les cas compliqués.

Notez également que vous pouvez facilement convertir un IO Int en un Int. Si foo produit un IO Int, et que bar prend un seul paramètre comme paramètre Int et renvoie une valeur non-IO, voici ce qui se passe:

foo >>= return . bar

Ou en utilisant la notation do:

do 
    a <- foo
    return $ bar a
1
nponeccop

J'ai utilisé SipHash à cette fin

import Data.ByteArray.Hash
import Data.ByteString (pack, cons)
import Data.Word (Word8, Word64)

random :: Word64 -> Word64 -> [Word8] -> Double
random a b cs = (subtract 1) . (/(2**63)) . read . drop 8 . show $ sipHash (SipKey a b) (pack cs)

les lectures et abandons 8 ainsi que les émissions servent à supprimer un nouveau type qui ne supporte pas (ou n’a pas pris en compte lorsque j’ai implémenté) les castings.

maintenant vous voulez un Int dans une plage. Integer est plus facile cependant:

random :: Word64 -> Word64 -> [Word8] -> (Integer, Integer) -> Integer
random a b cs (low,high) = let
    span = high-low
    Rand = read . drop 8 . show $ sipHash (SipKey a b) (pack cs)
    in (Rand `mod` span) + low

bien sûr, vous obtiendrez toujours le même nombre pour les mêmes arguments à chaque fois. Vous devrez donc les modifier, c’est-à-dire que vous transmetterez toujours des arguments sans simplement renvoyer de valeurs. Que cela soit plus pratique qu’une monade dépend (c’était pour moi)

voici comment je me suis assuré que les arguments (en particulier l'argument [Word8]) seraient toujours différents:

foo bytes = doSomethingRandom bytes
bar bytes = map (\i -> foo (i:bytes)) [1..n]
baz bytes = doSomething (foo (0:bytes)) (bar (1:bytes))
0
some idiot