使用break-s/continue-s将命令控制流转换为haskell

使用break-s/continue-s将命令控制流转换为haskell,haskell,functional-programming,imperative-programming,Haskell,Functional Programming,Imperative Programming,考虑以下命令式代码,它在3位数的乘积中找到最大的回文(是的,这是“18世纪杰出数学家项目”网站的首批任务之一): curmax=0 对于范围内的i(999100): 对于范围(999100)内的j: 如果((i*j)

考虑以下命令式代码,它在3位数的乘积中找到最大的回文(是的,这是“18世纪杰出数学家项目”网站的首批任务之一):

curmax=0
对于范围内的i(999100):
对于范围(999100)内的j:
如果((i*j)
在我目前学习Haskell的过程中,我的问题是,如何将这个(以及基本上任何包含比普通迭代更复杂的内容的命令式构造,例如中断、继续、临时变量等等)翻译成Haskell

我的版本是

maxpal i curmax
    | i < 100 = curmax
    | otherwise = maxpal (i-1) (innerloop 999)
    where 
        innerloop j
            | (j < 100) || (p < curmax) = curmax
            | pal p = p
            | otherwise = innerloop (j-1)
            where p = i*j
main = print $ maxpal 999 0
maxpal i curmax
|i<100=curmax
|否则=maxpal(i-1)(内部循环999)
哪里
内环j
|(j<100)| |(p
但这看起来我们还在城里


那么,你有什么建议,处理这种情况的方法是什么?在我看来,一个范围对应一个列表。例如:

f = [999,998..100]
现在
f
被定义为从999到100的数字序列

for
循环对应于不同的功能概念,具体取决于您在每次迭代中所做的工作。有时
贴图
是合适的模拟,有时是
折叠
,有时是其他东西。通常情况下,这是多种因素的结合。在本例中,您有效地合并了两个列表。在Haskell中实现这一点的一种方法是列表理解:

g = [(x * y) | x <- f , y <- f]

g=[(x*y)| x如果我们取消所有优化,只需将100到999之间的所有数字组合相乘,过滤掉非回文并取最大值,我们可以非常简洁地编写函数,如下所示:

maximum $ filter pal [x*y | x <- [100..999], y <- [100..999]]

最大$filter pal[x*y | x与Daniel和sepp2k的答案类似:

惰性函数编程允许您以比您在命令式控制流中看到的更模块化的方式编写程序,就像您在问题中看到的那样。 例如,形成因子999…100的列表,然后是所有乘积,然后过滤以仅保留回文,然后计算最大值。 由于懒惰,这些中间列表将只在需要时出现,并将逐步循环使用

有关更多解释和示例,请参阅约翰·休斯的经典论文

maxpal::Int
maxpal=最大值[i*j | i Bool
pal=pal.show
鲍尔::(等式a)=>[a]->Bool
palL xs=xs==反向xs

Gah.被sepp2k打败了,但我会回答你的一般问题:

临时变量也可以使用state monad或ST monad(如果有很多的话)来表示。FP通常在简洁性和清晰性方面占优势,但在某些情况下却不是这样,例如,当有几个局部变量需要处理时


懒惰可以模拟许多中断,但在处理IO时,通常必须使用显式递归。然而,“列表”软件包(来自Hackage)在允许您以函数式方式编写IO循环方面相当聪明。

这里没有一刀切的答案。但让我们来看看这个具体示例:

首先,考虑外环:我们总是做全范围,我们只关心最后的最大值,所以这很容易:

outerLoop = foldl innerLoop 0 [999,998..100]
在内环中,我们有一些i值和电流最大值。现在我们只关心i*j大于电流最大值的范围:

innerLoop curmax i = foldr checkMax curmax [999*i, 998*i .. curmax]
在核心逻辑中,我们得到一个i*j的值,我们知道它总是大于或等于当前的最大值,所以需要检查下一个值,看看它是否是回文:如果是,我们就完成了,因为序列减少了。如果不是,推迟决定:

checkMax ij defer = if pal ij then ij else defer

所以,从功能上考虑,你应该寻找方法,将问题分解成函数,而不是循环和步骤

因此,如果我们有一个函数
maxWhere f xs
,它返回最大的
x
,其中
f x
为真,我们可以写:

maxpal = maxWhere pal [x * y | x <- [999,998..100], y <- [999,998..100]]
但是,如果
f
比比较更昂贵,这是不好的,因为我们将比原来调用f更多。我们可以使用fold将过滤器和最大值组合成一个单一过程,并获得与命令式代码相同的行为

maxWhere f xs = foldl' r 0 xs
    where r a x
       | x > a     = if f x then x else a
       | otherwise = a
在这里使用零作为一个神奇的小数字是可怕的,但在这种情况下是有效的


(我真的很想拼写候选编号列表
(*)[99998..100][99998..100]
,但这可能会带来不必要的复杂性。)

这种循环很容易理解列表,如下所示:

import Data.Maybe
import Data.List

maxpal i curmax
    | i < 100 = curmax
    | otherwise = maxpal (i-1) newmax
    where newmax = fromMaybe curmax (find pal bigger)
          bigger = takeWhile (> curmax) (map (*i) [999, 998 ..])
maximum [x*y | x <- [999..100], y <- [999..100],isPalindrome (x*y)]
isPalindrome x = xs == reverse xs
  where xs = show x
maximum [x*y | x <- [999..100], y <- [x..100],isPalindrome (x*y)]
这确实足够快了,虽然有点不明智,所以首先我们会注意到我们检查了两次数字。假设a*b是最大的回文,然后我们会检查
x==a,y==b
,和
x==b,y==a
。因此首先我们通过将搜索的数字限制在ca来阻止这种情况ses,其中x>=y,如下所示:

import Data.Maybe
import Data.List

maxpal i curmax
    | i < 100 = curmax
    | otherwise = maxpal (i-1) newmax
    where newmax = fromMaybe curmax (find pal bigger)
          bigger = takeWhile (> curmax) (map (*i) [999, 998 ..])
maximum [x*y | x <- [999..100], y <- [999..100],isPalindrome (x*y)]
isPalindrome x = xs == reverse xs
  where xs = show x
maximum [x*y | x <- [999..100], y <- [x..100],isPalindrome (x*y)]
值得注意的是,我们的限制,
(x*x)
,实际上意味着从现在开始,
[x*x,(x-1)*x..curr]
将是空的。正如您所看到的,python代码中的中断所强制的所有边界都适用于x上的一次迭代(使用递归)在一个x*y值列表上查找。这看起来可能不太好,但在我看来,它更明确地说明了我们对x和y的限制

运行它,我们得到:

*Main> search 999 0
906609

事实证明,在
x*x
时停止是一个非常好的主意,因为906609的平方根是952…

正如斯蒂芬·泰特利(stephen tetley)在他的评论中指出的那样,在FP中,可以使用连续传递样式来处理复杂的控制流(
Cont
monad加上它的
callCC
,它在某种程度上类似于
break
import Control.Monad.Cont

pal n = sn == reverse sn
    where sn = show n

range = [99999,99998..10000]

mfoldM a r f = foldM f a r  

curmaxm = (`runCont` id) $ mfoldM 0 range $ \m i ->
            callCC $ \break ->
                mfoldM m range $ \m j -> do
                  let ij = i*j
                  if ij < m
                     then break m
                     else return $
                          if pal ij then ij else m
import Data.List
import Data.Maybe
import Control.Monad

curmax = fromJust $ foldM it 0 range
    where 
      it m i = (find pal . takeWhile (>m) . map (*i) $ range) `mplus` return m
import Control.Monad.Cont
import Control.Monad.State.Strict

solcs = runCont (execStateT comp 0) id
    where   
      comp = forM_ range $ \i -> callCC $ \break ->
                forM_ range $ \j -> do
                  let ij = i*j
                  m <- get
                  when (ij < m) (break ())
                  when (pal ij) (put ij)  
implement main (argc, argv) = let
  fun loop1 (i: int): void =
    if i <= 9 then loop2 (i, i) else ()

  and loop2  (i: int, j: int): void =
    if j <= 9 then begin
      if i < j then begin
        print ", ";
        print "("; print i; print ", "; print j; print ")";
        loop2 (i, j+1)
      end
    end else begin
      print_newline ();
      loop1 (i+1)
    end
  in
    loop1 0
  end
for (i = 0; i <= 9; i += 1) {
  for (j = i; j <= 9; j += 1) {
    if (i < j) printf (", ") ; printf ("(%i, %i)", i, j) ;
  } /* for */
  printf ("\n") ;
} /* for */

return 0 ;