26

如何在不替换的情况下从一组数字 ( [1, 2, 3]) 中采样,直到我点击x?我的计划是打乱列表[1, 2, 3]并将其砍在x

-- chopAt 3 [2, 3, 1] == [2, 3]
-- chopAt 3 [2, 1, 3] == [2, 1, 3]
-- chopAt 3 [3, 1, 2] == [3]
chopAt _ [] = []
chopAt x (y:ys)
  | x /= y    = y : chopAt x ys
  | otherwise = [y]

但是我不知道如何洗牌(或理解 Monads)。

-- sample without replacement from [1, 2, 3] until one hits a 3
-- x <- shuffle [1, 2, 3]
-- print (chopAt 3 x)
main = do
-- shuffle [1, 2, 3]
  print (chopAt 3 [1, 3, 2])
4

6 回答 6

50

使用随机甚至MonadRandom来实现你的随机播放。这里有一些很好的答案

但这确实是可操作的。这是幕后发生的事情。

一世。

随机性是您在 Haskell 中首先遇到并且必须处理杂质的地方之一——这似乎令人反感,因为洗牌和样本看起来如此简单,并且不觉得它们应该与打印到物理屏幕或发射核武器,但通常purity == referentially transparent和参考透明的随机性是无用的。

random = 9 -- a referentially transparent random number

所以我们需要一个关于随机性的不同想法来让它变得纯粹。

二、

用于提高可重复性(非常重要)的科学代码中的典型“作弊”是修复实验的随机种子,以便其他人可以验证每次运行代码时他们得到完全相同的结果。这正是参考透明性!让我们试试看。

type Seed = Int
random :: Seed -> (Int, Seed)
random s = (mersenneTwisterPerturb s, splitSeed s)

其中mersenneTwisterPerturb是从Seeds 到的伪随机映射IntsplitSeed是从Seeds 到Seeds 的伪随机映射。请注意,这两个函数都是完全确定的(并且是透明的),所以random也是如此,但是我们可以像这样创建一个无限的、惰性的伪随机流

randomStream :: Seed -> [Int]
randomStram s = mersenneTwisterPerturb s : randomStream (splitSeed s)

同样,这个流是基于Seed值的确定性的,但是只看到流而不看到种子的观察者应该无法预测它的未来值。

三、

我们可以使用随机整数流来打乱列表吗?当然我们可以,通过使用模运算。

shuffle' :: [Int] -> [a] -> [a]
shuffle' (i:is) xs = let (firsts, rest) = splitAt (i `mod` length xs) xs
                     in (head rest) : shuffle' is (firsts ++ tail rest)

或者,为了使其更加独立,我们可以预先组合我们的流生成函数来得到

shuffle :: Seed -> [a] -> [a]
shuffle s xs = shuffle' (randomStream s) xs

另一个“种子消耗”引用透明的“随机”功能。

四。

所以这似乎是一个重复的趋势。事实上,如果你浏览这个模块System.Random,你会看到很多我们上面写的函数(我已经专门化了一些类型类)

random :: (Random a) => StdGen -> (a, StdGen)
randoms :: (Random a) => StdGen -> [a]

其中Random是可以随机生成的事物的类型类,StdGen是 的一种Seed。这已经足够实际的工作代码来编写必要的改组函数了。

shuffle :: StdGen -> [a] -> [a]
shuffle g xs = shuffle' (randoms g) xs

还有一个IO函数newStdGen :: IO StdGen可以让我们构建一个随机种子。

main = do gen <- newStdGen
          return (shuffle gen [1,2,3,4,5])

但是你会注意到一些烦人的事情:如果我们想要做出不同的随机排列,我们需要不断改变 gen

main = do gen1 <- newStdGen
          shuffle gen1 [1,2,3,4,5]
          gen2 <- newStdGen
          shuffle gen2 [1,2,3,4,5]

          -- using `split :: StdGen -> (StdGen, StdGen)`
          gen3 <- newStdGen
          let (_, gen4) = split gen3
          shuffle gen3 [1,2,3,4,5]
          let (_, gen5) = split gen4
          shuffle gen4 [1,2,3,4,5]

StdGen这意味着如果你想要不同的随机数,你要么必须做大量的簿记,要么留在 IO 中。由于引用透明性,这“有意义”再次 - 一组随机数必须彼此随机因此您需要将信息从每个随机事件传递到下一个。

不过,这真的很烦人。我们能做得更好吗?

五。

好吧,通常我们需要的是一种让函数接收随机种子然后输出一些“随机”结果和下一个种子的方法。

withSeed :: (Seed -> a) -> Seed -> (a, Seed)
withSeed f s = (f s, splitSeed s)

结果类型withSeed f :: Seed -> (a, Seed)是一个相当普遍的结果。让我们给它一个名字

newtype Random a = Random (Seed -> (a, Seed))

而且我们知道我们可以在 in 中创建有意义Seed的 s IO,所以有一个很明显的函数可以将Random类型转换为IO

runRandom :: Random a -> IO a
runRandom (Random f) = do seed <- newSeed
                          let (result, _) = f seed
                          return result

现在感觉我们有了一些有用的东西——类型的随机值的概念aRandom a只是Seeds 上的一个函数,它返回下一个Seed,这样以后的Random值就不会全部相同。我们甚至可以制作一些机器来组合随机值并Seed自动传递

sequenceRandom :: Random a -> Random b -> Random b
sequenceRandom (Random fa) (Random fb) = 
    Random $ \seed -> let (_aValue, newSeed) = fa seed in fb newSeed

但这有点傻,因为我们只是扔掉_aValue。让我们组合它们,使得第二个随机数实际上在很大程度上取决于第一个随机值。

bindRandom :: Random a -> (a -> Random b) -> Random b
bindRandom (Random fa) getRb = 
    Random $ \seed -> let (aValue, newSeed) = fa seed
                          (Random fb)       = getRb aValue
                      in fb newSeed

我们还应该注意,我们可以对Random值做“纯”的事情,例如,将随机数乘以 2:

randomTimesTwo :: Random Int -> Random Int
randomTimesTwo (Random f) = Random $ \seed -> let (value, newSeed) = f seed
                                              in (value*2, newSeed)

我们可以将其抽象为 Functor 实例

instance Functor Random where
  fmap f (Random step) = Random $ \seed -> let (value, newSeed) = step seed
                                           in (f value, newSeed)

现在我们可以创建很酷的随机效果,比如布朗运动

brownianMotion :: Random [Int]
brownianMotion = 
   bindRandom random $ \x -> 
       fmap (\rest -> x : map (+x) rest) brownianMotion

六、

这触及了我一直在写的整个问题的核心。随机性可以IO很好地存在于 monad 中,但它也可以作为更简单的Randommonad 单独存在。我们可以立即编写实例。

instance Monad Random where
  return x = Random (\seed -> (x, seed))
  rx >>= f = bindRandom rx f

因为它是一个单子,我们得到免费的do符号

brownianMotion' = do x <- random
                     rest <- brownianMotion'
                     return $ x : map (+x) rest

你甚至可以幻想并称之为runRandom单子同态,但这是一个非常不同的话题。

所以,回顾一下

  1. Seed引用透明语言中的随机性需要
  2. 购物车Seed很烦人
  3. “提升”和“绑定”随机值有一个共同的模式,可以Seed自然地路由 s
  4. 该模式形成一个单子

真正简短的回答是,您可能希望使用随机甚至MonadRandom来实现您的随机播放。通常,它们对于“采样”会派上用场。

干杯!

于 2013-02-04T18:40:45.173 回答
4

你在寻找排列吗?

似乎也cropAt可以通过takeWhile. 我个人更喜欢标准组合器而不是手工制作。

于 2013-02-04T17:41:15.583 回答
4

要对列表进行洗牌,请使用random-shuffle库:

import System.Random (newStdGen)
import System.Random.Shuffle (shuffle')

main = do
  rng <- newStdGen

  let xs = [1,2,3,4,5]

  print $ shuffle' xs (length xs) rng
于 2019-02-19T00:42:13.687 回答
2

从我的 Haskell 学习开始,您可能会发现一些简单的解决方案。如果说实话,我还处于起步阶段或稍微落后 ;-)

import System.Random
import Control.Applicative

shuffle :: [a] -> IO [a]
shuffle [] = return []
shuffle lst = do
    (e, rest) <- pickElem <$> getIx
    (e:) <$> shuffle rest
    where
    getIx = getStdRandom $ randomR (1, length lst)
    pickElem n = case splitAt n lst of
        ([], s) -> error $ "failed at index " ++ show n -- should never match
        (r, s)  -> (last r, init r ++ s)
于 2013-02-05T13:16:26.833 回答
2

每个人似乎都曾在某个时间点遇到过这种情况。这是我对问题的快速解决方案:

import System.Random

shuffle :: [a] -> IO [a]
shuffle [] = return []
shuffle xs = do randomPosition <- getStdRandom (randomR (0, length xs - 1))
                let (left, (a:right)) = splitAt randomPosition xs
                fmap (a:) (shuffle (left ++ right))

请注意,复杂度为 O(N^2),因此对于较大的列表来说这是非常低效的。另一种方法是通过使用可变数组(线性复杂度)来实现Fisher-Yates shuffle :

import Data.Array.IO
import System.Random

swapElements_ :: (MArray a e m, Ix i) => a i e -> i -> i -> m ()
swapElements_ arr i j = do a <- readArray arr i
                           b <- readArray arr j
                           writeArray arr i b
                           writeArray arr j a
                           return ()

shuffle :: [a] -> IO [a]
shuffle xs = do let upperBound = length xs
                arr <- (newListArray (1, upperBound) :: [a] -> IO (IOArray Int a)) xs
                mapM_ (shuffleCycle arr) [2..upperBound]
                getElems arr
  where shuffleCycle arr i = do j <- getStdRandom (randomR (1, i))
                                swapElements_ arr i j
于 2015-03-14T21:47:03.740 回答
1

将此函数添加到您的代码中,然后像这样调用它shuffle (mkStdGen 5) [1,2,3,4,5]

import System.Random

shuffle gen [] = [] 
shuffle gen list = randomElem : shuffle newGen newList
  where 
   randomTuple = randomR (0,(length list) - 1) gen
   randomIndex = fst randomTuple
   newGen      = snd randomTuple
   randomElem  = list !! randomIndex
   newList     = take randomIndex list ++ drop (randomIndex+1) list

或者像这样将它包含在你的do块中

main = do
     r <- randomIO
     str <- getLine 
     putStrLn (show (shuffle (mkStdGen r) str))
于 2020-02-28T17:41:26.923 回答