Random 从数组或列表中拾取随机元素

Random 从数组或列表中拾取随机元素,random,purescript,Random,Purescript,免责声明:我使用的是PureScript,但也添加了Haskell标记,因为我认为这在两种语言中可能表现相同,并且Haskell社区更大 我想从数组中选择一个随机元素,重复地。每次我都希望有一个新的随机选择,但重复调用的值总是相同的。似乎随机函数在每次运行程序时只计算一次 这在后续调用时始终返回相同的名称: import Data.Array (length, unsafeIndex) import Effect.Random (randomInt) import Effect.Unsafe (

免责声明:我使用的是PureScript,但也添加了Haskell标记,因为我认为这在两种语言中可能表现相同,并且Haskell社区更大

我想从数组中选择一个随机元素,重复地。每次我都希望有一个新的随机选择,但重复调用的值总是相同的。似乎随机函数在每次运行程序时只计算一次

这在后续调用时始终返回相同的名称:

import Data.Array (length, unsafeIndex)
import Effect.Random (randomInt)
import Effect.Unsafe (unsafePerformEffect)
import Partial.Unsafe (unsafePartial)

pick :: forall a. Array a -> a
pick arr = unsafePartial $ unsafeIndex arr i where
    i = unsafePerformEffect $ randomInt 0 (length arr - 1)

name :: String
name = pick names
使用此解决方法,每次都会返回一个新的随机拾取:

import Data.Array (length, unsafeIndex)
import Effect.Random (randomInt)
import Effect.Unsafe (unsafePerformEffect)
import Partial.Unsafe (unsafePartial)

pick :: forall a. Array a -> a
pick arr = unsafePartial $ unsafeIndex arr i where
    i = unsafePerformEffect $ randomInt 0 (length arr - 1)

-- without the dummy argument, this is not re-evaluated
-- on subsequent calls and always returns the same name
name :: Unit -> String
name _ = pick names
我在用,还有


我觉得这是一个丑陋的黑客。实现这一点的正确方法是什么?

一个每次调用时都会执行不同操作的函数与Haskell的设计相反,基于您必须导入的名称“Effect.Unsafe”,我假设PureScript也是如此。要编写这样一个函数而不使用不安全包中的东西进行“欺骗”是不可能的,任何与这样一个函数交互的人都会头疼

相反,给你的函数一个更诚实的类型签名。我不知道PureScript的等价物,但在Haskell中,它是这样的(改编自):

pick::[a]->可能(IO a)
选择[]=Nothing
选择xs=Just$do

我你可能想在你的问题上加上“随机”标签

我不知道PureScript,对于新手来说文档似乎很少,但在Haskell圈子里,这似乎是一个相当常见的抱怨:随机数生成函数。通常关于随机数的说法适用

然而,Haskell有一个关于随机数生成的既定原则。这并不一定涉及IO,即使在Haskell中IO monad碰巧“承载”了一个随机数生成器

在Haskell中,您需要:

import  System.Random
import  Control.Monad.Random
问题是,给定相同参数的函数总是返回相同的结果

解决方案是,您需要将随机数生成器的初始状态作为函数参数包含,并将新的更新状态作为结果的一部分返回。这就是Haskell函数的作用。第一个参数是输出范围。如果您的数组有100个索引在0到99之间的元素,那么这将是一个2元组:(0,99)

一旦有一个函数返回一个随机值,就可以轻松地构建第二个函数返回任意数量的值,例如:

randomRn :: (RandomGen g, Random a) => (a, a) -> Int -> g -> ([a], g) 
randomRn range count g0 =
    if (count <= 0)
       then  ([], g0)  -- no values and no change
       else  let (a0, g1) = randomR  range g0
                 (as, gf) = randomRn range (count-1) g1  -- recursive call
             in
               (a0:as, gf)  
程序输出:

Random indexes v1: [9,56,13,9,38,86,62,18,77,4,66,65,27,33,68,55,94,15,77,45]
现在,根据品味、风格和问题的复杂性,你可能会发现国家的明确存在令人烦恼,并想以某种方式隐藏它。为此,Haskell使用状态单子的一个变体,称为。使用这种方法,您可以使用如下代码定义返回随机值列表的一元操作:

iterateMn :: MonadRandom mr => (Int, Int) -> Int -> mr [Int]
iterateMn range count =
    if  (count <= 0)  then
        return []  -- no action required
    else
        do
            v1 <- getRandomR range
            vs <- iterateMn range (count-1)
            return (v1:vs)
详情如下:


PureScript随机数生成工具似乎构建在Javascript工具之上。根据您的要求有多严格,它可能足够好,也可能不够好。例如,您可能决定咬紧牙关,实现一个PureScript版本的随机数生成器。众所周知,它的统计特性非常强,它的状态具有非常小的内存大小,因此可以灵活地适应函数式编程语言。显然,已经有几种Lisp实现可用。

多亏了@amalloy的回答,我找到了我认为适合我的案例的很好的解决方案

关键是保持
效应
不受随机数生成的影响(
效应
对应于Haskell中的
IO
),而不是使用
unsafeperformefect
丢弃它<代码>效果
反映了一个事实,即该值的计算涉及到一些副作用,并且每次可能会产生不同的结果。这正是我想要的。因此,有了这个新的类型签名,它现在的行为和我预期的一样:
name::Effect String
。每次效果为“运行”时,它都会从数组中随机选择一个新字符串

正如@amalloy所建议的,我现在也使用非空数组

pick :: forall a. NonEmptyArray a -> Effect a
pick arr = do
    i <- randomInt 0 (length arr - 1)
    let item = arr !! i
    case item of
        Just one -> pure one
        Nothing -> pure $ head arr
        -- still have to handle the Maybe from (!!) which is
        -- a bit annoying since this obviously can never be Nothing

name :: Effect String
name = pick names

main :: Effect Unit
main = do
    name >>= log
    name >>= log
    name >>= log
    -- new pick each time

pick::for all a。非空射线a->效果a
选择arr=do
我是纯洁的
无->纯$head arr
--仍然需要处理来自(!!)的可能
--有点烦人,因为这显然不可能什么都不是
名称::效果字符串
名称=选择名称
主要影响单位
main=do
名称>>=日志
名称>>=日志
名称>>=日志
--每次都有新的选择

如果你真的确定
i
在那里,你总是可以使用
fromJust
unsafePartial
——开玩笑:-P为了减轻负担,你可以把它变成一行:
pick arr=randomInt 0(length arr-1)index arr>>fromMaybe(head arr)
。。。。又开玩笑了:-P但是说真的-我认为这个助手连同
shuffle
等应该进入一些随机的实用程序库,所以可能值得在这里添加一个注释:。你怎么认为?
iterateMn :: MonadRandom mr => (Int, Int) -> Int -> mr [Int]
iterateMn range count =
    if  (count <= 0)  then
        return []  -- no action required
    else
        do
            v1 <- getRandomR range
            vs <- iterateMn range (count-1)
            return (v1:vs)
    let action          = iterateMn range count    -- monadic action object
        (indexes2, gf2) = runRand action g0        -- go generate indexes
pick :: forall a. NonEmptyArray a -> Effect a
pick arr = do
    i <- randomInt 0 (length arr - 1)
    let item = arr !! i
    case item of
        Just one -> pure one
        Nothing -> pure $ head arr
        -- still have to handle the Maybe from (!!) which is
        -- a bit annoying since this obviously can never be Nothing

name :: Effect String
name = pick names

main :: Effect Unit
main = do
    name >>= log
    name >>= log
    name >>= log
    -- new pick each time