Calcule o máximo possível de uma lista em um horário fixo
Eu quero escrever uma função que leva um limite de tempo (em segundos) e uma lista e calcula tantos elementos da lista quanto possível dentro do limite de tempo.
Minha primeira tentativa foi primeiro escrever a seguinte função, que cronometra uma computação pura e retorna o tempo decorrido junto com o resultado:
import Control.DeepSeq
import System.CPUTime
type Time = Double
timed :: (NFData a) => a -> IO (a, Time)
timed x = do t1 <- getCPUTime
r <- return $!! x
t2 <- getCPUTime
let diff = fromIntegral (t2 - t1) / 10^12
return (r, diff)
Eu posso então definir a função que quero em termos disso:
timeLimited :: (NFData a) => Time -> [a] -> IO [a]
timeLimited remaining [] = return []
timeLimited remaining (x:xs) = if remaining < 0
then return []
else do
(y,t) <- timed x
ys <- timeLimited (remaining - t) xs
return (y:ys)
Isso não está certo, no entanto. Mesmo ignorando os erros de temporização e os erros de ponto flutuante, esta abordagem nunca para o cálculo de um elemento da lista depois de iniciada, o que significa que pode (e de fato, normalmente) irá ultrapassar seu limite de tempo.
Se, em vez disso, eu tivesse uma função que pudesse fazer um curto-circuito na avaliação se demorasse demais:
timeOut :: Time -> a -> IO (Maybe (a,t))
timeOut = undefined
então eu poderia escrever a função que eurealmente quer:
timeLimited' :: Time -> [a] -> IO [a]
timeLimited' remaining [] = return []
timeLimited' remaining (x:xs) = do
result <- timeOut remaining x
case result of
Nothing -> return []
Just (y,t) -> do
ys <- timeLimited' (remaining - t) xs
return (y:ys)
Minhas perguntas são:
Como escrevotimeOut
?Existe uma maneira melhor de escrever a funçãotimeLimited
, por exemplo, aquele que não sofre de erro de ponto flutuante acumulado de adicionar diferenças de horário várias vezes?