Подтвердить что ты не робот

Генерация случайного целого числа в диапазоне в Haskell без начального числа

Как я могу генерировать случайное число в Haskell из диапазона (a, b) без использования какого-либо семени?

Функция должна возвращать Int, а не IO Int. У меня есть функция X, которая принимает и Int и другие аргументы и выводит то, что не является IO.

Если это невозможно, как я могу генерировать семя, используя библиотеку Time, и генерировать случайное число в диапазоне с помощью mkStdGen?

Любая помощь будет действительно оценена.

4b9b3361

Ответ 1

Функция не может вернуть Int без IO, если только она не является чистой функцией, т.е. с учетом того же ввода, вы всегда получите тот же результат. Это означает, что если вы хотите случайное число без IO, вам нужно будет взять семя в качестве аргумента.

  • Если вы решили взять семя, оно должно быть типа StdGen, и вы можете использовать randomR для создания числа из него. Используйте newStdGen для создания нового семени (это нужно сделать в IO).

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

    Результат randomR - это набор, в котором первым элементом является случайное значение, а второе - новое семя для использования для создания большего количества значений.

  • В противном случае вы можете использовать randomRIO для получения случайного числа непосредственно в монаде IO, при этом все вещи StdGen заботятся о вас:

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

Ответ 2

Не прибегая ко всем видам небезопасных практик, такая функция не может иметь тип Int, а не тип IO Int или что-то подобное. Функции (или, в данном случае, константы) типа Int являются чистыми, что означает, что каждый раз, когда вы "вызываете" функцию (извлекаете значение константы), вы гарантированно получаете то же значение "возвращено".

Если вы хотите иметь другое, случайно выбранное значение, возвращаемое при каждом вызове, вам нужно использовать IO -monad.

В некоторых случаях вы можете захотеть иметь одно случайно созданное значение для всей программы, то есть такое, которое с точки зрения программы ведет себя так, как если бы оно было чистым значением. Каждый раз, когда вы запрашиваете значение в рамках одного и того же запуска программы, вы получаете одно и то же значение обратно. Поскольку вся программа по существу является IO -action, вы можете затем генерировать это значение один раз и передавать его, но это может показаться немного неуклюжим. Можно утверждать, что в этой ситуации по-прежнему безопасно связывать значение с константой верхнего уровня типа Int и использовать unsafePerformIO для построения этой константы:

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

Ответ 3

fmap yourFunctionX $ randomRIO (a, b)

или

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

Тогда результат будет иметь тип IO whateverYourFunctionXReturns.

Если вы import Control.Applicative, вы можете сказать

yourFunctionX <$> randomRIO (a, b)

или

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

который вы можете найти более четким

Ответ 4

Обратите внимание, что вы можете получить бесконечный список случайных значений, используя монаду ввода-вывода, и использовать этот [Int] в функциях не-ввода-вывода. Таким образом, вы не должны нести с собой семена, но все равно должны нести список, конечно. К счастью, существует множество функций обработки списков, упрощающих такую многопоточность, и вы все равно можете использовать монаду State в сложных случаях.

Также обратите внимание, что вы можете легко преобразовать IO Int в Int. Если foo выдает IO Int, а bar принимает Int как единственный параметр и возвращает значение, отличное от IO, будет сделано следующее:

foo >>= return . bar

Или используя обозначение do:

do 
    a <- foo
    return $ bar a

Или используя fmap (монады являются функторами, а <$> - инфиксной версией fmap):

bar <$> foo

Ответ 5

Я использовал SipHash для этой цели

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)

чтение и отбрасывание 8 и показ служат для того, чтобы отбросить новый тип, который doesnt (или didnt, когда я его реализовал) поддерживает любое литье

теперь вы хотите Int в диапазоне. Целое проще:

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

конечно, вы все равно будете получать одинаковый номер для тех же самых аргументов каждый раз, поэтому вам нужно будет их изменять, т.е. вы все равно проходите вокруг аргументов, а также не возвращаете значения. Является ли это более удобным, чем монада, зависит (для моей цели это было)

вот как я убедился, что аргументы (в частности, аргумент [Word8]) всегда будут разными:

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