2012-10-11 2 views
4

STArray를 사용하여 Fisher-Yates 셔플 알고리즘을 작성하려고합니다. 인터넷에서 찾은 다른 모든 예제와 달리 네이티브 목록 사용을 피하려고합니다. 난 그냥 배열을 뒤섞고 싶다.STArray와 함께`getBounds '를 사용하는 방법?

randShuffleST arr gen = runST $ do 
    _ <- getBounds arr 
    return (arr, gen) 

arr가 STArray이고 gen 타입 (RandomGen의 g)의 발생 상태가 될 것이다

이것은 I 가진 것에있다.

나는 MArray의 getBounds을 사용할 수있게 되 MArray에 정의 된 (MArray (STArray s) e (ST s))instance declaration에 의존 할 수 있지만, GHCi가 randShuffleST의 유형을 추론 할 수없는 기대했다. 내가 지금처럼`runST '에 대한 호출을 제거하면, 흥미롭게도

Could not deduce (MArray a e (ST s)) 
    arising from a use of `getBounds' 
from the context (Ix i) 
    bound by the inferred type of 
      randShuffleST :: Ix i => a i e -> t -> (a i e, t) 
    at CGS/Random.hs:(64,1)-(66,25) 
Possible fix: 
    add (MArray a e (ST s)) to the context of 
    a type expected by the context: ST s (a i e, t) 
    or the inferred type of 
     randShuffleST :: Ix i => a i e -> t -> (a i e, t) 
    or add an instance declaration for (MArray a e (ST s)) 
In a stmt of a 'do' block: _ <- getBounds arr 
In the second argument of `($)', namely 
    `do { _ <- getBounds arr; 
     return (arr, gen) }' 
In the expression: 
    runST 
    $ do { _ <- getBounds arr; 
     return (arr, gen) } 

: 그것은 잘 컴파일

randShuffleST arr gen = do 
    _ <- getBounds arr 
    return (arr, gen) 

, 유형 서명

randShuffleST :: (Ix i, MArray a e m) => a i e -> t -> m (a i e, t) 

으로는 실패합니다. 저는 아치 리눅스에서 GHC 7.4.2를 사용하고 있습니다.

코드에 대한 이해를 돕기 위해 응답에 명시적인 형식 시그니처를 주시면 감사하겠습니다.

EDIT : Antal S-Z의 답변이 마음에 들지만 솔직하게 솔직하게 이해하지 못하기 때문에 선택할 수 없습니다. 어쩌면 내 자신의 문제를 더 잘 이해하면 미래에 내 자신의 질문에 대답 할 것입니다 ... 감사합니다.

답변

3

다음은 내부 피셔 - 예이츠 (Durstenfeld 또는 Knuth Shuffle이라고하는 이라고 생각합니다.)를 구현하는 한 가지 방법입니다. runST은 호출되지 않고 runSTArray이 대신 호출되며 한 번만 호출됩니다.

import Data.Array 
import Data.Array.ST 
import Control.Monad.ST 
import Control.Monad 
import System.Random 

fisherYates :: (RandomGen g,Ix ix, Random ix) => g -> Array ix e -> Array ix e 
fisherYates gen a' = runSTArray $ do 
    a <- thaw a' 
    (bot,top) <- getBounds a 
    foldM (\g i -> do 
    ai <- readArray a i 
    let (j,g') = randomR (bot,i) g 
    aj <- readArray a j 
    writeArray a i aj 
    writeArray a j ai 
    return g') gen (range (bot,top))  
    return a 

참고 알고리즘이 자리에서 수행되지만, 함수 제 복사 입력 사본의 알고리즘을 수행하기 전에 (thaw 기능 사용으로 인해)에서 주어진 배열.배열을 복사 방지하기 위해 적어도 두 가지 옵션이 있습니다

  1. 당신이
    는 그 입력 배열이 결코 확신하는 경우 (이름에서 알 수 있듯이) 안전하지 않은 만 사용할 수 있습니다 사용 unsafeThaw을, 다시 사용하십시오. 게으른 평가로 인해 이것은 에 대한 보증이되지 않습니다.

  2. fisherYates이 유형 (RandomGen g,Ix ix, Random ix) => g -> STArray s ix e -> ST s (STArray s ix e) 보자하고 ST 모나드 내부에서 적절한 피셔 - 예이츠 알고리즘을 필요로 만 runST로 최종 답변을주고 모든 작업을 수행합니다. runST의 순위 2 유형 randShuffleST에 의미있는 형식을 제공에서 당신을 방지하기 때문에

+0

+1 작업 솔루션입니다. 정확히 내가 요구 한 것은 아니지만 (runST 사용), 실제적이고 간결하며 실제적인 예제를 숭배한다. –

7

아마도 함수에 runST을 사용하지 않아야합니다. runST은 내부적으로 돌연변이를 사용하지만 순수한 인터페이스를 가진 일부 계산 외부에서는 한 번 사용해야합니다. 배열을 임의대로 뒤섞고, STArray s i e -> ST s() (또는 더 일반적인 유형)과 같은 유형을 가진 다음, 순수한 인터페이스를 제시하기 위해 runST을 사용하는 다른 기능을 사용하는 셔플 기능이 필요할 수도 있습니다. 그 함수는 아마도 값을 복사해야 할 것이다). 일반적으로 ST의 목표는 STRef이고 STArrayrunST 호출에서 벗어나 다른 호출에서 사용할 수 없다는 것입니다.

runST없이 함수에 대해 유추 된 유형은 괜찮습니다. (IO 배열, ST 배열, STM 배열, unboxed 배열 등에서는 더 잘 작동합니다). 그러나 명시 적 유형 서명을 지정하면 추론 오류로 인해 더 쉽게 시간을 가질 수 있습니다.

+0

[Haskell wiki] (http : //www.haskell.)에서 랜덤 셔플 (Fischer-Yates, 나는 생각합니다. org/haskellwiki/Random_shuffle)은 runST를 내부적으로 사용하며 순수 인터페이스 (shuffle ')를 가지고 있습니다. 그 기능의 배열 전용 버전을 만들려고합니다. RunST를 사용하여 ST 모나드 셔플 함수를 호출하는 다른 함수를 사용하면 wiki의 shuffle 함수가 runST를 사용하고 순수한 인터페이스를 제공한다는 점을 고려할 때 어떤 차이가 있는지 생각하는 이유를 이해할 수 없습니다. 정확히 당신이 제안한대로. –

+0

위키 (또는 다른 대답)에 나와있는 예제와 매우 다른 현재 위치 셔플을 요청했습니다. 이 예제는 입력리스트를 배열에 복사하고, 그 자리에서 배열을 셔플 한 다음 다시 복사합니다. 실제 in-place shuffle을 원한다면, 순수한 인터페이스를 유지하면서 내부적으로 'ST'를 사용할 수 없습니다. 반면에, in-place 코드가 있다면, 순수한 인터페이스를 제공하기 위해'runST'를 몇 개의 복사본 (또는'runSTArray')과 함께 사용하는 것이 쉽습니다. – shachaf

+0

@shachaf, 나는 당신이 나의 대답을 언급하고 있었다고 생각한다. 예, 먼저 배열을 복사 한 다음 내부 셔플을 수행하지만, 이것을 고치면 단순히 '해동'과 'unsafethaw'를 교환하는 것입니다. – HaskellElephant

6

발생합니다. (코드로 두 번째 문제가 생겼다 : 변경 가능한 ST 배열은 ST 모나드 외부에 의미있게 존재할 수 없으므로, runST 내부에서 하나를 반환하는 것은 불가능하며, 순수 함수로 전달하는 것은 불가능하다. "흥미롭지 않지만"독자적으로 혼란 스러울 수도 있습니다.이 답변의 최하단 부분을 참조하십시오.

그럼 형식 서명을 적어 둘 수없는 이유를 알아 보겠습니다. 예를 들어 ST 안에 있고 마지막에 한 번만 runST을 사용하십시오. 여러분이 작성한 것과 같은 기능을 작성하는 가장 좋은 방법은 I agree with shachaf입니다. 이 작업을 수행하면 대답의 맨 아래에 코드를 성공적으로 작성하는 방법을 보여주는 샘플 코드가 포함되어 있습니다. 그러나 왜 당신이 오류를 범하는지 이해하는 것은 흥미로운 일입니다. 당신이 얻는 것과 같은 오류는 여러분이이 방법으로 코드를 작성하고 싶지 않은 몇 가지 이유입니다! 같은 오류 메시지를 생성하는 기능의 단순화 된 버전에서

이의로 시작하자 먼저 보면 :

이제
bounds arr = runST (getBounds arr) 

,의는 bounds에 유형을 제공하기 위해 노력하겠습니다. 확실한 선택은 우리가 arrMArray해야합니다 알고

bounds :: (MArray a e (ST s), Ix i) => a i e -> (i,i) 
bounds arr = runST (getBounds arr) 

이며, 우리는 상관하지 않습니다 어떤 요소 또는 인덱스 유형은 (한 그 지수가 Ix에로) 가지고 있지만, 우리가 살아야 것을 알고있다 ST 모나드 안에 있습니다. 이게 효과가 있겠지요, 그렇죠? 그렇게 빨리! Could not deduce (MArray a e (ST s1)) :

ghci> :set -XFlexibleContexts +m 
ghci> :module + Control.Monad.ST Data.Array.ST 
ghci> let bounds :: (MArray a e (ST s), Ix i) => a i e -> (i,i) 
ghci|  bounds arr = runST (getBounds arr) 
ghci| 
<interactive>:8:25: 
    Could not deduce (MArray a e (ST s1)) 
     arising from a use of `getBounds' 
    from the context (MArray a e (ST s), Ix i) 
     bound by the type signature for 
       bounds :: (MArray a e (ST s), Ix i) => a i e -> (i, i) 
     at <interactive>:7:5-38 
    ... 

은 잠깐

? 어디에서 왔습니까 s1‽ 어디에서나 그런 변수를 언급하지 않습니다! 대답은 의 정의에서 runST에서 오는 것입니다. 일반적으로 runST 유형 (편의를 위해 일부 유형 변수 이름 바꾸기) runST :: (forall σ. ST σ α) -> α; 여기에서 사용할 때는 (forall σ. ST σ (i,i)) -> (i,i) 유형으로 제한했습니다. 여기서 일어나는 것은 forall이 람다와 같다는 것입니다 (실제로는 람다 임). 국번이 괄호 안에 σ입니다. 그래서 때 유형 ST s (i,i)getBounds arr 반환 뭔가, 우리는 (i,i)α를 통합 할 수 있습니다 ---하지만 σ이 범위에 포함되지 않기 때문에 우리는 sσ를 통합 할 수 없습니다.GHC, 그것은 모호성을 제거하기 위해 s s1에 이름을 변경 있도록 runST의 형태 변수는 sa하지 σα을, 그리고 당신이보고있는이 형태 변수 입니다.

오류가 공평합니다. 특정 s에 대해 MArray a e (ST s)이 해당한다고 주장했습니다. 그러나 runST에 대해 각각s에 해당해야합니다. 그러나 오류는 매우 불분명합니다. 실제로 참조 할 수없는 새로운 유형의 변수를 도입하기 때문에 (어쨌든 결코 도움이되지는 않지만 "가능한 수정"은 의미가 없습니다).

명백한 질문은 "올바른 형식 서명을 쓸 수 있습니까?"입니다. 대답은 "... 일종의." (하지만 당신은 아마 싶지 않아.) 원하는 유형은 다음과 같을 것이다

ghci> :set -XConstraintKinds -XRank2Types 
ghci> let bounds :: (forall s. MArray a e (ST s), Ix i) => a i e -> (i,i) 
ghci|  bounds arr = runST (getBounds arr) 
ghci| 
<interactive>:170:25: 
    Could not deduce (MArray a e (ST s)) 
     arising from a use of `getBounds' 
    from the context (forall s. MArray a e (ST s), Ix i) 
... 

이 제약이 MArray a e (ST s)모든s을 위해 보유하고 있다고하지만, 우리는 여전히 유형의 오류가 발생합니다. "GHC does not support polymorphic constraints to the left of an arrow"이 나타나고 그 정보를 찾으려고 노력하면서 실제로 동일한 문제가 발생하여 오류를 설명하고 다음 해결 방법을 제공하는 an excellent blog post at "Main Is Usually A Function"을 발견했습니다. (그들은 또한 잘못된 일이 불가능하다는 것을 분명하게 보여주는 "잘못된 클래스 선언"이라는 우수한 오류 메시지를 얻었습니다. 이것은 아마도 GHC 버전이 다르기 때문일 것입니다.)

아이디어는 우리가 더 많은 것을 원할 때 일반적인 것입니다. 이제

ghci> :set -XNoFlexibleContexts -XNoConstraintKinds 
ghci> -- We still need -XRank2Types, though 
ghci> :set -XGADTs 
ghci> data MArrayE a e m where 
ghci| MArrayE :: MArray a e m => MArrayE a e m 
ghci| 
ghci> 

, 우리는의 값을 가질 때마다하십시오 GADT 사용하여 우리가 (AB)에 의한 이러한 유형 클래스의 존재에 대한 명시적인 증거를 제공하기 위해, GHC에 내장 된 시스템에서 얻을 수있는 유형의 클래스 제약의 MArrayE a e m을 입력하면 값이 MArrayE 생성자로 구성되어야합니다. 이 생성자는 MArray a e m 제약 조건이있을 때만 호출 할 수 있으므로 MArrayE의 패턴 일치는 해당 제약 조건을 다시 사용할 수있게합니다. (유일한 다른 가능성은 해당 유형의 값이 정의되지 않았기 때문에 패턴 일치가 실제로 필요한 이유입니다.) 이제 우리는이를 bounds 함수에 대한 명시적인 인수로 제공 할 수 있으므로 bounds MArrayE arr :

ghci> :set -XScopedTypeVariables 
ghci> let bounds :: forall a e i. 
ghci|    Ix i => (forall s. MArrayE a e (ST s)) -> a i e -> (i,i) 
ghci|  bounds evidence arr = runST (go evidence) 
ghci|  where go :: MArrayE a e (ST s) -> ST s (i,i) 
ghci|    go MArrayE = getBounds arr 
ghci| 
ghci> -- Hooray! 

본문을 자체 기능으로 분해하고 거기에서 패턴 일치해야하는 기이함에 유의하십시오. 당신이 bounds의 인수 목록에서 패턴 일치하면 evidences이 특정 값으로 너무 일찍 고정되므로이 값을 지정해야합니다. 그리고 더 높은 순위 타입에 대한 추론이 어렵 기 때문에 go에 대한 명시 적 타입을 제공해야합니다. 이는 범위가 지정된 타입 변수를 필요로합니다.

그리고 마지막으로, 원래의 코드로 돌아 : 내가 처음에 말했듯이

ghci> let randShuffleST :: forall a e i g. Ix i => (forall s. MArrayE a e (ST s)) 
ghci|           -> a i e 
ghci|           -> g 
ghci|           -> (a i e, g) 
ghci|  randShuffleST evidence arr gen = runST $ go evidence 
ghci|  where go :: MArrayE a e (ST s) -> ST s (a i e,g) 
ghci|    go MArrayE = do _ <- getBounds arr 
ghci|        return (arr, gen) 
ghci| 
ghci> -- Hooray again! But... 

이제 하나의 문제가 해결 남아있다. 위 코드에서 forall s. MArray a e (ST s) 제약 조건을 만족할 수 없기 때문에 forall s. MArrayE a e (ST s) 유형의 값을 생성하는 방법이 절대로 존재하지 않습니다.같은 이유로 원래 코드에서 STArrayST 외부로 반환하는 함수를 작성할 수 없기 때문에 유형 오류 없이도 randShuffleST을 쓸 수 없습니다.

이러한 두 가지 문제의 이유는 동일합니다 : an STArray's first parameter is the state thread it lives on. STArray에 대한 MArray 인스턴스는 instance MArray (STArray s) e (ST s)이므로 항상 유형이 ST s (STArray s i e)입니다. runST :: (forall s. ST s a) -> a 이후로 runST mySTArrayAction을 실행하면 s이 "누출"될 수 있습니다.

runSTArray :: Ix i => (forall s. ST s (STArray s i e)) -> Array i e

로보고 그 언 박싱 친구

runSTUArray :: Ix i => (forall s. ST s (STUArray s i e)) -> UArray i e.

당신은 또한 당신이 그 당신이 이제까지 당신의 변경 가능한 배열에 전화 할게 마지막 기능의 약속으로, 같은 일을

unsafeFreeze :: (Ix i, MArray a e m, IArray b e) => a i e -> m (b i e)

을 사용할 수 있습니다; freeze 함수는이 제한을 완화하지만 배열을 복사해야합니다. 똑같은 토큰으로,리스트가 아닌 배열을 함수의 순수한 버전으로 전달하고자한다면, 아마도 또한

thaw :: (Ix i, IArray a e, MArray b e m) => a i e -> m (b i e);

을 사용하면 unsafeThaw을 사용하면 제어 할 수없는 변경 불가능한 배열을 전달하기 때문에 여기에서 재앙이 될 것입니다. 이것은 우리 모두에게 같은 것을 제공하기 위해 결합합니다 :

ghci> :set -XNoRank2Types -XNoGADTs 
ghci> -- We still need -XScopedTypeVariables for our use of `thaw` 
ghci> import Data.Array.IArray 
ghci> let randShuffleST :: forall ia i e g. (Ix i, IArray ia e) 
ghci|     => ia i e 
ghci|     -> g 
ghci|     -> (Array i e, g) 
ghci|  randShuffleST iarr gen = runST $ do 
ghci|  marr <- thaw iarr :: ST s (STArray s i e) 
ghci|  _  <- getBounds marr 
ghci|  iarr' <- unsafeFreeze marr 
ghci|  return (iarr', gen) 
ghci| 
ghci> randShuffleST (listArray (0,2) "abc" :: Array Int Char) "gen" 
(array (0,2) [(0,'a'),(1,'b'),(2,'c')],"gen") 

이 소요 O (N) 입력 불변의 배열을 복사하는 시간이 있지만-와 가 O (1) 시간을 동결 최적화-소요 STArrayArray이 후드에서 같기 때문에 출력을위한 가변 배열. 특히 문제이 적용

, 우리는 다음과 같은 한 :

{-# LANGUAGE FlexibleContexts #-} 
import System.Random 
import Control.Monad 
import Control.Applicative 
import Control.Monad.ST 
import Data.Array.ST 
import Data.STRef 
import Data.Array.IArray 

updateSTRef :: STRef s a -> (a -> (b,a)) -> ST s b 
updateSTRef r f = do 
    (b,a) <- f <$> readSTRef r 
    writeSTRef r a 
    return b 

swapArray :: (MArray a e m, Ix i) => a i e -> i -> i -> m() 
swapArray arr i j = do 
    temp <- readArray arr i 
    writeArray arr i =<< readArray arr j 
    writeArray arr j temp 

shuffle :: (MArray a e (ST s), Ix i, Random i, RandomGen g) 
     => a i e -> g -> ST s g 
shuffle arr gen = do 
    rand   <- newSTRef gen 
    [email protected](low,_) <- getBounds arr 
    when (rangeSize bounds > 1) . 
    forM_ (reverse . tail $ range bounds) $ \i -> 
     swapArray arr i =<< updateSTRef rand (randomR (low,i)) 
    readSTRef rand 

-- Two different pure wrappers 

-- We need to specify a specific type, so that GHC knows *which* mutable array 
-- to work with. This replaces our use of ScopedTypeVariables. 
thawToSTArray :: (Ix i, IArray a e) => a i e -> ST s (STArray s i e) 
thawToSTArray = thaw 

shufflePure :: (IArray a e, Ix i, Random i, RandomGen g) 
      => a i e -> g -> (a i e, g) 
shufflePure iarr g = runST $ do 
    marr <- thawToSTArray iarr 
    g' <- shuffle marr g 
    iarr' <- freeze marr 
    return (iarr',g') 

shufflePure' :: (IArray a e, Ix i, Random i, RandomGen g) 
      => a i e -> g -> (Array i e, g) 
shufflePure' iarr g = 
    let (g',g'') = split g 
     iarr' = runSTArray $ do 
        marr <- thaw iarr -- `runSTArray` fixes the type of `thaw` 
        void $ shuffle marr g' 
        return marr 
    in (iarr',g'') 

을 다시 말하지만, 당신이 shufflePureData.Array.Unsafe.unsafeFreeze와 동결을 대체 할 수; Array i e 인 경우 배열을 복사 할 필요가 없기 때문에 속도가 빨라집니다. runSTArray 함수는 unsafeFreeze을 안전하게 랩하므로 에는 문제가되지 않습니다. (두 개는 동일하며 PRNG 분할에 대한 세부 정보를 제공합니다.)

여기에서 무엇을 볼 수 있습니까? 중요한 것은 변경 가능한 코드 만이 변경 가능한 배열을 참조하며 가변적 인 배열 ()이 안에있는 내용을 반환합니다. shuffle은 내부 셔플을 수행하므로 PRNG 만 배열을 반환 할 필요가 없습니다. 순수한 인터페이스를 구축하려면, thaw 가변 배열에 변경 불가능한 배열을 넣고 으로 바꾸고 그 다음에 freeze을 불변의 배열로 되 돌린다. 이는 중요합니다. 변경된 데이터가 순수한 세계로 다시 유출되는 것을 방지합니다.전달 된 배열은 변경할 수 없기 때문에 직접 변경할 수 없습니다. 반대로 가변 변경할 수 있기 때문에 변경할 수없는 배열로 변경할 수있는 배열을 직접 반환 할 수는 없으며 누군가 변경할 수 있다면 어떨까요?

위의 오류는 모두 runST의 부적절한 사용으로 인해 발생하기 때문에 위의 오류는 발생하지 않습니다. runST의 사용을 제한하고 순수한 결과를 얻은 후에 만 ​​실행하면 모든 내부 상태 스레딩이 자동으로 발생할 수 있습니다. runST은 등급 2 유형의 유일한 기능이므로 심각한 유형의 이상 함을 유발할 수있는 유일한 곳입니다. 다른 모든 것들은 표준 타입 기반 추론을 필요로합니다. 아마도 s 상태 스레드 매개 변수를 일관성있게 유지하려고 생각할 지 모르지만 말입니다.

및 LO와 보라 :

*Main> let arr10 = listArray (0,9) [0..9] :: Array Int Int 
*Main> elems arr10 
[0,1,2,3,4,5,6,7,8,9] 
*Main> elems . fst . shufflePure arr10 <$> newStdGen 
[3,9,0,5,1,2,8,7,6,4] 
*Main> elems . fst . shufflePure arr10 <$> newStdGen 
[3,1,0,5,9,8,4,7,6,2] 
*Main> elems . fst . shufflePure' arr10 <$> newStdGen 
[3,9,2,6,8,4,5,0,7,1] 
*Main> elems . fst . shufflePure' arr10 <$> newStdGen 
[8,5,2,1,9,4,3,0,7,6] 

성공, 긴 지속! (길이가 너무 길어서 정말이 답변의 길이는 유감입니다.)

+0

나는 내 질문이 나를 깊은 토끼 구멍으로 이끌 것이라는 것을 전혀 몰랐다 ... 나는 당신의 대답을 완전히 이해할 시간이 필요하다. 그러나 많은 확장 기능을 사용하고 너무 많은 농구를 뛰어 넘어야한다는 사실 때문에 STArray를 제자리에 배치하는 것이 대부분의 하스 켈러에게 사실상 불가능하다고 믿게합니다. 그러나 "ST 배열이 ST 모나드 밖에서 의미있게 존재할 수 없다"는 말을 해 주셔서 대단히 감사합니다. - 이것만으로 많은 도움이되었습니다. 귀하의 설명의 폭과 깊이에 +1. –

+0

@opert : 만약 내가 "[당신에게] STArray의 위치를 ​​뒤섞기가 대부분의 하스 켈러에게 사실상 불가능하다고 믿게한다면, 나는 당신에게 나쁜 대답을했다. 결국, [HaskellElephant의 해결책] (http://stackoverflow.com/a/12849728/237428)을 보아라. 그렇게 이해했다. 내 대답의 목표는 * 왜 * 당신이 실수를하는지를 안내하는 것이 었습니다. 이야기의 도덕적 성격 (나는 좀 더 분명히해야한다)은'runST'의 잘못이라는 것입니다. 'runST'를 외부에두기위한 충고를 따르면 코드가 아주 간단해질 것입니다. 내 대답을 편집하여 예제를 포함시켜 보겠습니다. –