当累加器满足一定条件时,如何从haskell中的折叠函数中跳出?

当累加器满足一定条件时,如何从haskell中的折叠函数中跳出?,haskell,functional-programming,fold,Haskell,Functional Programming,Fold,在将someFunction应用于列表的每个元素之后,我计算列表的总和,如下所示: sum (map someFunction myList) someFunction非常占用资源,因此为了优化它,如果超过某个阈值,我想停止计算总和 似乎我需要使用折叠,但如果累加器达到阈值,我不知道如何打破它。我的猜测是以某种方式组合fold和takeWhile,但我不确定如何组合。您可以尝试创建自己的sum函数,或者称之为boundedSum 一个整数上限 求和的[整数] 与上限进行比较的“汇总到此点”值

在将
someFunction
应用于列表的每个元素之后,我计算列表的总和,如下所示:

sum (map someFunction myList)
someFunction
非常占用资源,因此为了优化它,如果超过某个阈值,我想停止计算总和

似乎我需要使用折叠,但如果累加器达到阈值,我不知道如何打破它。我的猜测是以某种方式组合
fold
takeWhile
,但我不确定如何组合。

您可以尝试创建自己的
sum
函数,或者称之为
boundedSum

  • 一个
    整数
    上限

  • 求和的
    [整数]

  • 与上限进行比较的“汇总到此点”值

  • 并返回列表的总和

        boundedSum :: Integer -> [Integer] -> Integer -> Integer
        boundedSum upperBound (x : xs) prevSum =
            let currentSum = prevSum + x
                in
            if   currentSum > upperBound
            then upperBound
            else boundedSum upperBound xs currentSum
        boundedSum upperBound [] prevSum =
            prevSum
    
    我认为这样,在当前元素超过上限之前,如果求和,就不会“吃掉”更多的列表


    编辑:建议使用比我更好的技术的答案,而且问题本身看起来与你的问题非常相似。

    其中一个选项是使用函数,该函数返回
    foldl
    的中间计算列表

    foldl (\b a -> b + if b > someThreshold then 0 else a) 0 (map someFunction myList)
    
    因此,
    scanl1(+)(map someFunction myList)
    将返回计算的中间和。由于
    Haskell
    是一种惰性语言,因此在需要它之前,它不会计算
    myList
    的所有值。例如:

    take 5 $ scanl1 (+) (map someFunction myList)
    
    head $ dropWhile (< 1000) $ scanl1 (+) [1..1000000000]
    
    将计算
    someFunction
    5次并返回这5个结果的列表

    之后,当某个条件为
    True
    时,您可以使用或并停止计算。例如:

    take 5 $ scanl1 (+) (map someFunction myList)
    
    head $ dropWhile (< 1000) $ scanl1 (+) [1..1000000000]
    
    head$dropWhile(<1000)$scanl1(+)[1..100000000]
    

    当数字总和达到1000并返回
    1035

    时,将停止计算。这是一种可能的解决方案:

    last . takeWhile (<=100) . scanl (+) 0 . map (^2) $ [1..]
    

    last。takeWhile(使用有界加法运算符,而不是使用
    foldl
    (+)

    foldl (\b a -> b + if b > someThreshold then 0 else a) 0 (map someFunction myList)
    
    因为Haskell是非严格的,所以只有对
    someFunction
    的调用才是计算
    if-then-else
    所必需的,它们本身也会被计算。
    fold
    仍然遍历整个列表

    > foldl (\b a -> b + if b > 10 then 0 else a) 0 (map (trace "foo") [1..20])
    foo
    foo
    foo
    foo
    foo
    15
    
    sum[1..5]>10
    ,您可以看到
    trace“foo”
    只执行5次,而不是20次


    但是,您不应该使用
    Data.Foldable中的严格版本
    foldl'

    另一种技术是使用
    foldM
    一起捕获提前终止效果。
    表示提前终止

    import Control.Monad(foldM)
    
    sumSome :: (Num n,Ord n) => n -> [n] -> Either n n
    sumSome thresh = foldM f 0
      where
        f a n 
          | a >= thresh = Left a
          | otherwise   = Right (a+n)
    
    要忽略退出条件,只需使用
    任一id
    组合即可

    sumSome' :: (Num n,Ord n) => n -> [n] -> n
    sumSome' n = either id id . sumSome n
    

    这将满足您的要求,而无需构建中间列表,因为
    scanl'
    会(而且
    scanl
    甚至会在其上导致thunks累积):


    参见相关。

    类似的内容,使用
    直到::(a->Bool)->(a->a)->a->a
    ,从前奏曲开始

    sumUntil :: Real a => a -> [a] -> a
    sumUntil threshold u = result
    
        where
    
        (_, result) = until stopCondition next (u, 0)
    
        next :: Real a => ([a], a) -> ([a], a)
        next ((x:xs), y) = (xs, x + y)
    
        stopCondition :: Real a => ([a], a) -> Bool
        stopCondition (ls, x) = null ls || x > threshold
    
    然后申请

    sumUntil 10 (map someFunction myList)
    

    几乎可以,但是使用scanl而不是fold。如果总和超过阈值,或者如果资源密集型函数的结果超过阈值?可能需要考虑的情况是,可能永远不会达到阈值,在这种情况下,
    head
    将出错。也许我会使用
    find
    而不是
    head
    dropWhile
    查找(>=1000)(scanl1(+)[1..)
    这很酷,看起来像巫术,但应用于无限列表时它确实会挂起。我认为跟踪显示,
    a
    只是不是强制的,尽管整个列表都被遍历了。啊,对了。我不太明白为什么这似乎起作用。我一直认为这种懒散的把戏是一种典型的“突围”方式从褶皱中。