Haskell- 如何在递归函数中跟踪计数器
Haskell- How to keep track of counter in recursive function
我在尝试跟踪函数中的计数器变量时遇到了一些困难。
我创建了一个函数,将数字作为单个参数并递归地将数字乘以二,将所有乘以二的数字加在一起,下面的代码更清楚我打算做什么:
sum :: Float -> Float
sum x = x + sum (2*x)
但是,我面临的困难是我希望函数只递归十次。所以我希望它在十个数字相加后停止。我尝试使用计数器来跟踪函数递归的次数,但无济于事。
这是我试过的:
sumTen :: Float -> Float
sumTen x n
where
n=10
|n == 0 = x
|otherwise = x + sumTen (2*x) (n-1)
我意识到上面的代码不起作用,计数器 n
在每次递归调用中都会被赋予值 10,这意味着它永远不会达到基本情况 n == 0
.
之所以如此困难,是因为必须使用一个参数调用 sumTen
。在上面的代码中,我试图给函数一个默认参数 n
和一个预定的值,但它显然不起作用。
谁能帮我在 'n' 次递归调用后停止递归函数?
可以使用辅助功能:
sumNRec x 0 = 0
sumNRec x times = x + sumNRec (x*2) (times - 1)
sumTen x = sumNRec x 10
sumTen :: Float -> Float
sumTen x = go 10 x
where
go n x
| n == 0 = x
| otherwise = x + go (n-1) (2*x)
这里有两个要点:go
是一个简单的两个参数的递归函数,它通过n
让你在正确的时间停下来。但是因为您不想公开该参数,所以顶级 sumTen
函数只是 go
的部分应用版本。你甚至可以这样写:
sumTen = go 10
where go n x = -- ...
你喜欢哪一款是一种风格选择,真的。
也许是这样:
sumTen x = sum $ take (10+1) $ iterate (* 2) x
让我们用一般的方式来解决这个问题。从您的原始功能开始:
sum :: Float -> Float
sum x = x + sum (2*x)
第一步,添加一个额外的 rec
参数。这个rec
代表"recursive call",是我们要调用的函数,而不是函数体中的所有递归调用。具体来说,只需将出现在定义右侧的任何 sum
替换为 rec
:
sumK :: (Float -> Float) -> Float -> Float
sumK rec x = x + rec (2*x)
现在,如果我们希望递归函数执行零次,我们将得到 id
。为了只递归一次,我们可以使用 sumK id
since
sumK id x = x + id (2*x) = x + 2*x
递归两次:sumK (sumK id)
since
sumK (sumK id) x = x + sumK id (2*x) = x + (2*x) + 2*(2*x)
等等:要递归 n
次,我们只需 运行 sumK (... (sumK id)...)
,其中 sumK
被应用 n
次。等价地,这可以写成组合sumK . sumK . ... . sumK $ id
。因此,要获得足够的生成列表 [sumK,sumK,...]
,组合它们,并在末尾应用 id
。
sum10 :: Float -> Float
sum10 = compose (replicate 10 sumK) id
compose :: [a -> a] -> a -> a
compose = foldr (.) id
如果愿意,可以写一个小通用帮手:
recurseOnly :: Int -> ((a->a)->(a->a)) -> (a->a)
recurseOnly n funK = compose (replicate n funK) id
sum10 :: Float -> Float
sum10 = recurseOnly 10 sumK
我在尝试跟踪函数中的计数器变量时遇到了一些困难。
我创建了一个函数,将数字作为单个参数并递归地将数字乘以二,将所有乘以二的数字加在一起,下面的代码更清楚我打算做什么:
sum :: Float -> Float
sum x = x + sum (2*x)
但是,我面临的困难是我希望函数只递归十次。所以我希望它在十个数字相加后停止。我尝试使用计数器来跟踪函数递归的次数,但无济于事。
这是我试过的:
sumTen :: Float -> Float
sumTen x n
where
n=10
|n == 0 = x
|otherwise = x + sumTen (2*x) (n-1)
我意识到上面的代码不起作用,计数器 n
在每次递归调用中都会被赋予值 10,这意味着它永远不会达到基本情况 n == 0
.
之所以如此困难,是因为必须使用一个参数调用 sumTen
。在上面的代码中,我试图给函数一个默认参数 n
和一个预定的值,但它显然不起作用。
谁能帮我在 'n' 次递归调用后停止递归函数?
可以使用辅助功能:
sumNRec x 0 = 0
sumNRec x times = x + sumNRec (x*2) (times - 1)
sumTen x = sumNRec x 10
sumTen :: Float -> Float
sumTen x = go 10 x
where
go n x
| n == 0 = x
| otherwise = x + go (n-1) (2*x)
这里有两个要点:go
是一个简单的两个参数的递归函数,它通过n
让你在正确的时间停下来。但是因为您不想公开该参数,所以顶级 sumTen
函数只是 go
的部分应用版本。你甚至可以这样写:
sumTen = go 10
where go n x = -- ...
你喜欢哪一款是一种风格选择,真的。
也许是这样:
sumTen x = sum $ take (10+1) $ iterate (* 2) x
让我们用一般的方式来解决这个问题。从您的原始功能开始:
sum :: Float -> Float
sum x = x + sum (2*x)
第一步,添加一个额外的 rec
参数。这个rec
代表"recursive call",是我们要调用的函数,而不是函数体中的所有递归调用。具体来说,只需将出现在定义右侧的任何 sum
替换为 rec
:
sumK :: (Float -> Float) -> Float -> Float
sumK rec x = x + rec (2*x)
现在,如果我们希望递归函数执行零次,我们将得到 id
。为了只递归一次,我们可以使用 sumK id
since
sumK id x = x + id (2*x) = x + 2*x
递归两次:sumK (sumK id)
since
sumK (sumK id) x = x + sumK id (2*x) = x + (2*x) + 2*(2*x)
等等:要递归 n
次,我们只需 运行 sumK (... (sumK id)...)
,其中 sumK
被应用 n
次。等价地,这可以写成组合sumK . sumK . ... . sumK $ id
。因此,要获得足够的生成列表 [sumK,sumK,...]
,组合它们,并在末尾应用 id
。
sum10 :: Float -> Float
sum10 = compose (replicate 10 sumK) id
compose :: [a -> a] -> a -> a
compose = foldr (.) id
如果愿意,可以写一个小通用帮手:
recurseOnly :: Int -> ((a->a)->(a->a)) -> (a->a)
recurseOnly n funK = compose (replicate n funK) id
sum10 :: Float -> Float
sum10 = recurseOnly 10 sumK