【问题标题】:repeatedly applying a function until the result is stable重复应用一个函数,直到结果稳定
【发布时间】:2011-11-18 13:32:37
【问题描述】:

我想重复应用一个函数simplify',直到结果“稳定”(即simplify'(x) == x):

simplify :: Expr -> Expr
simplify expr =
    let iterations = iterate simplify' expr
        neighbours = zip iterations (tail iterations)
        simplified = takeWhile (\(a, b) -> a /= b) neighbours
    in  snd $ last ((expr, expr) : simplified)

simplify' :: Expr -> Expr

这对我来说似乎是一个常见问题。有没有更优雅的解决方案?

更新:我找到了一个更简单的解决方案,但我仍在寻找更优雅的解决方案:)

simplify expr =
    let next = simplify' expr
    in  if next == expr
        then expr
        else simplify next

【问题讨论】:

  • 我只写一个简单的递归函数。
  • “修复”是否适用于此?看来您正在寻找一个固定点。
  • @Tim:也许吧,但是fix 的文档让我大吃一惊。
  • @FredOverflow 我想为您指出正确的方向,但我对 Haskell 知之甚少。两个症结似乎是你需要一个惰性函数来修复收敛,并且它收敛到“最少定义”的固定点。不过,我不确定其中任何一个会如何影响您的情况。
  • @Tim: fix 找到 a different kind of fixed point

标签: function haskell repeat higher-order-functions


【解决方案1】:

下面是通过简单的模式匹配和递归实现的轻微概括。 converge 搜索无限列表,在一行中查找满足某个谓词的两个元素。然后它返回第二个。

converge :: (a -> a -> Bool) -> [a] -> a
converge p (x:ys@(y:_))
    | p x y     = y
    | otherwise = converge p ys

simplify = converge (==) . iterate simplify'

这使得使用近似等式进行收敛测试变得容易。

sqrt x = converge (\x y -> abs (x - y) < 0.001) $ iterate sqrt' x
    where sqrt' y = y - (y^2 - x) / (2*y) 

【讨论】:

  • 您的模式匹配与(x:y:ys) 有何不同,是否等效?
  • @Caridorc 假设列表是[1, 2, 3, 4]。使用(x:y:ys) 模式,您将得到ys = [3, 4]。使用(x:ys@(y:_)),您将获得ys = [2, 3, 4]ys@(y:_) 部分称为“as-pattern”,它将与整个(y:_) 模式匹配的值绑定到ys,为您提供整个列表,而(y:ys) 将第一个元素绑定到y其余的发给ys。尝试使用let (x:ys@(y:_)) = [1..4] in (x, y, ys) 及其在 GHCi 中的变体;你会看到它是如何工作的。
【解决方案2】:

sdcvvc 的代码简化为:

converge :: Eq a => (a -> a) -> a -> a
converge = until =<< ((==) =<<)

功能没有改变。该函数被交给((==) &gt;&gt;=),它给定参数(减少)从收敛和后来直到意味着在每次迭代中它会检查是否将当前a应用到f(f a == a)

【讨论】:

  • 这里最优雅的解决方案。
  • 这里的每个f a实际上是计算了两次吗?曾经在until 私下“内部”,另一个在until 用作停止条件的函数中?
【解决方案3】:
simplify = until (\x -> simplify' x == x) simplify'

until 是一个鲜为人知的 Prelude 函数。 (一个小缺点是这使用了大约 2n 次而不是大约 n 次 simplify'。)

然而,我认为最清晰的方法是修改您的版本以使用守卫以及在哪里:

simplify x | x == y    = x
           | otherwise = simplify y
           where y = simplify' x

另一种方式:

until' :: (a -> Maybe a) -> a -> a
until' f x = maybe x (until' f) (f x)

simplify :: Integer -> Integer
simplify = until' $ \x -> let y = simplify' x in
                           if x==y then Nothing else Just y

【讨论】:

    【解决方案4】:
    import Data.List.HT (groupBy)
    
    fst_stable = head . (!!1) . groupBy (/=)
    -- x, f(x), f^2(x), etc.
    mk_lst f x = let lst = x : (map f lst) in lst
    iter f = fst_stable . mk_lst f
    
    test1 = iter (+1) 1 -- doesn't terminate
    test2 = iter id 1 -- returns 1
    test3 = iter (`div` 2) 4 -- returns 0
    

    【讨论】:

      【解决方案5】:

      下面是一个可以使用的实现:

      applyTill :: (a -> bool) -> (a -> a) -> a -> a
      applyTill p f initial = head $ filter p $ scanl (\s e -> f s) initial [1..]
      

      示例用法:

      applyTill ( (==) stableExpr ) simplify' initExpr
      

      【讨论】:

      • applyTill 显然在 Prelude 中,名称为 until,正如我从 sdcvvc 的回答中了解到的那样。
      • 您可以使用(stableExpr ==)( (==) stableExpr )写成一个部分。
      猜你喜欢
      • 2012-01-21
      • 2020-11-17
      • 1970-01-01
      • 2017-12-23
      • 2021-04-21
      • 2022-01-26
      • 2016-04-16
      • 2021-10-03
      • 1970-01-01
      相关资源
      最近更新 更多