我想重复应用函数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
答案 0 :(得分:18)
这是通过简单的模式匹配和递归实现的轻微概括。 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)
答案 1 :(得分:17)
https://stackoverflow.com/a/7448190/1687259代码的简化将是:
converge :: Eq a => (a -> a) -> a -> a
converge = until =<< ((==) =<<)
功能不会改变。该函数被传递给((==) >>=)
,它从收敛和稍后给出参数(简化),直到意味着在每次迭代中它将检查是否将当前a
应用于f
,(f a == a)
答案 2 :(得分:9)
simplify = until (\x -> simplify' x == x) simplify'
until
是一个鲜为人知的Prelude函数。 (一个小缺点是它使用simplify'
大约2n次而不是大约n。)
但是,我认为最明确的方法是修改您的版本以使用警卫,其中:
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
答案 3 :(得分:1)
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
答案 4 :(得分:0)
以下是可以使用的一种此类实现:
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