나는 알 수없는 길이의 목록에서 임의의 원소를 선택하는 두 가지 함수를 작성했다. 첫 번째는 저수지 표본 추출 (크기가 1 인 저수지)을 사용하고 두 번째 표는 임의 색인을 선택하여 반환하는 목록의 길이를 가져옵니다. 웬일인지, 전자는 훨씬 빠릅니다.저수지 샘플링 대 목록의 길이를 얻고 무작위 원소를 채취하는 것
첫 번째 함수는 단일 순회를 사용하고 각 요소를 확률 (1/i)로 선택합니다. 여기서 i는 목록의 요소 색인입니다. 결과적으로 각 요소를 선택하는 확률이 높아집니다.
pickRandom :: [a] -> IO a
pickRandom [] = error "List is empty"
pickRandom (x:xs) = do
stdgen <- newStdGen
return (pickRandom' xs x 1 stdgen)
-- Pick a random number using reservoir sampling
pickRandom' :: (RandomGen g) => [a] -> a -> Int -> g -> a
pickRandom' [] xi _ _ = xi
pickRandom' (x:xs) xi n gen =
let (rand, gen') = randomR (0, n) gen in
if (rand == 0) then
pickRandom' xs x (n + 1) gen' -- Update value
else
pickRandom' xs xi (n + 1) gen' -- Keep previous value
번째 버전은 그 길이를 얻기 위해 일단 목록을 통과하고 동일한 확률 다시, 요소의 하나 얻을 0 입력리스트 (-1)의 길이 사이의 인덱스를 픽업. 목록 1.5의 탐색의 예상 번호 :
benchmarking Using reservoir
mean: 82.72108 ns, lb 82.02459 ns, ub 83.61931 ns, ci 0.950
benchmarking Using length
mean: 17.12571 ms, lb 16.97026 ms, ub 17.37352 ms, ci 0.950
에서 :
여기main :: IO()
main = do
gen <- newStdGen
let size = 2097152
inputList = getRandList gen size
defaultMain [ bench "Using length" (pickRandomWithLen inputList)
, bench "Using reservoir" (pickRandom inputList)
]
가 제거 출력 : 여기에
-- Traverses the list twice
pickRandomWithLen :: [a] -> IO a
pickRandomWithLen [] = error "List is empty"
pickRandomWithLen xs = do
gen <- newStdGen
(e, _) <- return $ randomR (0, (length xs) - 1) gen
return $ xs !! e
나는이 두 가지 기능을 벤치마킹에 사용하는 코드입니다 다른 용어로, 첫 번째 기능은 두 번째 기능보다 약 200 배 빠릅니다. 나는 런타임이 난수 생성과리스트 횡단 수 (1 대 1.5)에 의해 주로 영향을받을 것으로 예상했다. 그러한 큰 차이를 설명 할 수있는 다른 요인은 무엇입니까?
귀하의 벤치 마크 행동이 실제로 결과를 평가하지 않는