在此代码中:
import Data.Char
groupsOf _ [] = []
groupsOf n xs =
take n xs : groupsOf n ( tail xs )
problem_8 x = maximum . map product . groupsOf 5 $ x
main = do t <- readFile "p8.log"
let digits = map digitToInt $concat $ lines t
print $ problem_8 digits
我意识到在Haskell中,许多程序构造并且似乎存储了一些中间结果,例如上面代码中的groupsOf
列表。以上代码是Euler项目问题8的参考答案,取自Haskell website。原始问题要求在一个很长的数字链中连续5位数的最大乘积,例如45343231635723421312443535767983456
。
因此代码将计算所有产品并将其存储为列表。在其他语言中,我认为它们只会保留一个临时的最大值并丢弃任何较小的值。
groupsOf
真的存储了所有中间结果吗?如果问题扩大怎么办?它会分配太多内存吗?
答案 0 :(得分:12)
不,不是因为groupsOf
。那个只需要一次将一个组保留在内存中。但是,maximum
可能build up a large thunk因为它太懒,所以请确保使用-O
或-O2
进行编译,以便执行严格性分析,或使用foldl1' max
代替 1 。
1 foldl1'
位于Data.List
答案 1 :(得分:8)
我意识到在Haskell中很多程序构造并且似乎存储了一些中间结果,例如上面代码中的groupsOf列表。
“似乎”在这里说得很好,因为老实说这就是Haskell能够真正发挥作用的地方。事实上,由于懒惰,它可以占用更少的更少内存,而无需您进行复杂的微观管理。
关于惰性IO(例如readFile
)的一个很好的事情是,它只会根据需要读取尽可能多的文件,并且在您使用其内容时可以对文件进行垃圾收集。 (好吧,大致如此。这取决于你如何设置缓冲。)
这是一个关于如何执行程序的草图:
t <- readFile "p8.log"
为t :: String
创建一个thunk。可能检查文件是否存在,并以读取模式打开它。
let digits = map digitToInt $concat $ lines t
为digits :: [Int]
print $ problem_8 digits
一旦我们尝试执行此操作,所有工作都将完成。我们需要对problem_8 digits :: Int
进行全面评估才能打印出来。所以我们为problem_8 digits
创建了一个thunk并强制它。
maximum . map product . groupsOf 5 $ digits
此时,maximum
需要map product . groupsOf 5 $ digits
的前两个元素才能看到两者中的哪一个更大。因此,map product
需要查看要传递的groupsOf 5 digits
的前两个元素。
现在,groupsOf 5
需要digits
的前5个元素才能生成第一个元素。此时,可能会读取文件的第一行,并完成定义的转换。 groupsOf
可以构造它的第一个元素,可能是它的第二个元素(假设该行上有超过6个字符)。 groupsOf 5 digits
将前两个元素传递到链上,我们将product
映射到这两个元素上,然后maximum
检查两者中哪一个更大。我们保留结果,两者中的较大者。
此时(实际上比这一点早一些)我们可以完全丢弃中间结果。 groupOf 5 digits
的前两个元素现在完全没必要;我们永远不需要再次检查它们,垃圾收集器可以随时收集它们。该文件的前两个字符也将不再使用,并且可以被丢弃。
现在,这非常手持式,可能稍微不准确。但是你明白了吗? Haskell是懒惰的(技术上Haskell是“非严格的”,通常通过懒惰实现),这使得它的执行风格很多与任何严格的语言不同。这使得我们可以使用看似大量的中间表示和数据结构,而实际占用大量内存。好的编译器,比如GHC,可以像你不相信的那样优化内存使用。
答案 2 :(得分:3)
我不确定你的“中间结果”是什么意思,确切地说......但你的代码看起来很好。具体来说,您groupsOf
的实现是tail recursive modulo cons,所以我认为您不必担心堆栈溢出,如果这是您关心的问题。