如何避免递归调用耗尽RAM?欧拉计划193的Haskell内存优化
欧拉计划193问题的Haskell内存优化问题
问题背景
我正在解决欧拉计划193问题,目标是计算2^50以下的无平方因子数的数量。
采用的算法基于容斥原理:从所有数中减去包含单个质数平方因子的数,加上包含两个质数平方因子的数,减去包含三个的,以此类推,直到最多8个因子的组合。
为减少组合生成的工作量,我维护一个[(product, index)]列表,例如对于49×121×361的乘积,会保存(2140369,7),表示从索引8开始迭代,用2140369相乘得到更多组合(比如4个数的组合)。
这个算法在Python中可正常运行,耗时20秒,但我的Haskell实现会耗尽RAM,无法完成运行。
我推测问题出在列表base和base'一直占用内存——在Python中,我会在迭代开始时初始化base' = [],迭代结束时执行base = base'.copy(),内存中始终只保留这两个列表,但Haskell里没做到这一点。
核心疑问
- 如何减少Haskell实现的内存占用?
- 能否在递归调用之间销毁不再需要的列表?
- 能否使用流处理(比如惰性求值)来优化?
- 我认为
accum函数是需要修改的核心部分
我的Haskell代码
import qualified Data.ByteString.Lazy.Char8 as BLC import qualified Data.Vector.Unboxed as U import Data.Vector.Unboxed ((!)) import Data.Maybe (fromJust) import Data.List (foldl') primesSq :: Int -> [BLC.ByteString] -> [Int] primesSq lim [] = [] primesSq pmax (b:bs) | p < pmax = p^2 : primesSq pmax bs | otherwise = primesSq pmax [] where (p, _) = fromJust $ BLC.readInt b solve :: [BLC.ByteString] -> Int -> Int solve ls limit = total where pmax = floor . sqrt . fromIntegral $ limit ps2 = U.fromList $ primesSq pmax ls accumProd = U.takeWhile (< limit) $ U.scanl1 (*) ps2 rmax = U.length accumProd base1 = takeWhile (\b -> fst b <= limit `div` ps2!0) (zip (U.toList ps2) [0..]) accum :: [(Int,Int)] -> Int -> [Int] -> Int accum _ acc [] = acc accum base acc (r:rs) = accum base' acc' rs where base' = [(prod, j) | (p2, i) <- base , j <- [i+1..U.length ps2-1] , let prod = p2 * ps2 ! j , prod <= limit] acc' = acc + (-1)^r * sum [limit `div` p | (p,_) <- base'] total = limit - U.sum (U.map (div limit) ps2) + accum base1 0 [2..rmax] main :: IO () main = do ls <- BLC.lines <$> BLC.readFile "primes.txt" print $ solve ls (2^50)
注:我没有生成质数,而是从文本文件读取,同时练习文件读取操作。我也曾尝试用scanl'改写accum的递归,但结果相同且代码更冗长。
内容的提问来源于stack exchange,提问作者chapelo
相关产品推荐
相关产品推荐

