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