【问题标题】:Haskell recursion slow, what's the pitfall?Haskell递归很慢,有什么陷阱?
【发布时间】:2018-12-16 12:22:59
【问题描述】:

我是 Haskell 的初学者

data Recipes = Recipes Int Int Int [Int] deriving(Show) 

addRecipes :: Recipes -> Recipes
addRecipes (Recipes t e1 e2 list) = 
    let (e1c, e2c) = (list !! e1, list !! e2)
        newVal = e1c + e2c
        newList = list ++ (digits $ newVal)
        e1n = calcNewPos e1 e1c newList
        e2n = calcNewPos e2 e2c newList
    in Recipes t e1n e2n newList

calcNewPos :: Int -> Int -> [Int] -> Int    
calcNewPos i i2 list = (i + i2 + 1) `mod` (length list)

-- Borrowed:
-- https://stackoverflow.com/questions/3963269/split-a-number-into-its-digits-with-haskell
digits :: Int -> [Int]
digits = map (read . (:[])) . show

上面的代码是我省略的递归的一部分。 addRecipes 在递归调用中被一次又一次地调用。这是代码问题出现的解决方案(AoC 2018 第 14 天)。该代码产生了正确的结果,但速度非常慢。

我只是想知道问题出在哪里,上面的代码中什么地方效率极低?

我已经尝试优化 ++-operator 并将其替换为 : 并将其与数字调用相结合。我知道++: 慢,但真的是这样吗? (记住,我正在学习 Haskell,所以我想尽可能地修复这段代码,并知道为什么我不能这样写)

编辑:Recipes 中的列表越来越大

【问题讨论】:

  • 如果你需要大量的随机访问,比如list !! n,或者附加list ++ something,那么基本列表不是一个合适的数据结构。如果不了解总体情况,很难建议使用什么。也许一个数组就足够了,或者需要Seq / Vector。也许你可以不用随机访问,然后列表就可以了。
  • 嗯,好的,adventofcode.com/2018/day/14 如果有兴趣,这里有谜题......但这可能会有所帮助,我会调查!!,也许我可以以某种方式将其删除
  • 完成这个问题后,我意识到所有的工作都在序列的末尾完成,所以如果你把它保持在相反的顺序,你可能会避免只使用一个列表。 (并跟踪长度。)但很容易出错,使用Seq 会简单得多。
  • 只是为了扩展@DavidFletcher的评论,Seq指的是hackage.haskell.org/package/containers-0.6.0.1/docs/…
  • 它与 Seq 一起工作! 1 小时 -> 2 秒。谢谢,但是我得到:“getMBlocks:VirtualAlloc MEM_COMMIT 失败:分页文件太小,无法完成此操作。”在我的递归结束条件上:Seq.take 6 (Seq.reverse seq) == part2Input = Seq.length seq - 5。我会自己回答,但是如果您对最后一个障碍有指示,我会接受! :)

标签: haskell optimization


【解决方案1】:

我遵循了 cmets 中发布的建议;当您进行大量随机访问时,不要使用列表数据结构。所以我将列表替换为序列http://hackage.haskell.org/package/containers-0.6.0.1/docs/Data-Sequence.html

import qualified Data.Sequence as Seq

data Recipes = Recipes Int Int Int (Seq.Seq Int) deriving(Show) 

addRecipes :: Recipes -> Recipes
addRecipes (Recipes t e1 e2 seq) = 
let (e1c, e2c) = (Seq.index seq e1, Seq.index seq e2)
    newVal = e1c + e2c
    newSeq = (Seq.><) seq (Seq.fromList (digits newVal))
    e1n = calcNewPos e1 e1c newSeq
    e2n = calcNewPos e2 e2c newSeq
in Recipes t e1n e2n newSeq

calcNewPos :: Int -> Int -> (Seq.Seq Int) -> Int    
calcNewPos i i2 seq = (i + i2 + 1) `mod` (Seq.length seq)

它成功了,运行时间现在是合理的(从 1 小时到几秒)。谢谢评论!

【讨论】:

  • 你可以把(Seq.&gt;&lt;) seq (Seq.fromList (digits newVal))写成seq Seq.&gt;&lt; Seq.fromList (digits newVal)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-13
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 1970-01-01
  • 2011-02-26
相关资源
最近更新 更多