应用窗口函数来获取递归列表,我该怎么办?

时间:2013-01-30 06:09:41

标签: list haskell recursion loops

我刚遇到一个具有挑战性的问题(来自编程竞赛实践),其中包含递归序列,如下所示

给出3个数字 m n k 找到元素a [k]其中

a[0] = m
a[1] = n
a[i] = a[i-1] + a[i-2] ; if floor(i/2) mod 2 = 1
a[i] = a[i-1] - a[i-4] ; if floor(i/2) mod 2 = 0

示例案例:对于m = 2 n = 3 k = 6,答案为9

a[0] = 2
a[1] = 3
a[2] = 3 + 2 = 5
a[3] = 5 + 3 = 8
a[4] = 8 - 2 = 6
a[5] = 6 - 3 = 3
a[6] = 3 + 6 = 9
...

这就是我生成序列的方式(显然会消耗大量的堆栈,即使对于前100个元素也会超级慢)

 1 fbm :: Int → Int → Int → Int
 2 fbm m n 0 = m
 3 fbm m n 1 = n
 4 fbm m n x = let a = fbm m n (x-1)
 5                 b = fbm m n (x-2)
 6                 c = fbm m n (x-4)
 7             in case (x `div` 2) `mod` 2 of
 8                 1 →  a + b
 9                 0 →  a - c
10 
11 fbs m n = map (λx→fbm m n x) [0..]

由于在big(~1000 +)索引处找到元素所需的问题。我尝试通过尝试仅限于具有4个输入的函数来计算不同的方法,并在列表上递归地应用具有 4元素窗口的函数但是无法成功实现他们中的任何一个(某些东西意味着我无法弄明白该怎么做)

fs1 = map fst $ iterate next (a,b)
  where next (a,b) = something

fs2 = m:n:scanl (gen) 2 fs2 
  where gen [a,b,c,d] = something

fs3 = scanl (genx m n 0 0) (repeat 0)
  where genx a b c d = something

问题1:我的任何一种方法都是解决此问题的好方法吗? (+请给我一个如何做的例子)

问题2:如果我出错了,你会如何解决这类问题?

2 个答案:

答案 0 :(得分:2)

我想提出两个解决方案,它们也基于dbaupp在这里介绍的memoisation概念。与现有答案不同,以下解决方案使用索引而不是先前元素的值来计算列表的新元素。

第一个想法是关注

fbs :: Int -> Int -> [Int]
fbs m n = m : n : map (fbMake m n) [2 ..]

fbMake :: Int -> Int -> Int -> Int
fbMake m n = f
  where f i | (i `div` 2) `mod` 2 == 1 = (xs !! (i - 1)) + (xs !! (i - 2))
            | otherwise                = (xs !! (i - 1)) - (xs !! (i - 4))
        xs = fbs m n

此解决方案从其备忘的前辈中构建fbs m n列表的元素。不幸的是,由于列表的索引是O(n),因此它的执行效果相当差。

索引比列表更好?阵列开始发挥作用。这是第二个解决方案。

import Data.Array

fbs :: Int -> Int -> Int -> [Int]
fbs m n k = m : n : map (fbm m n k) [2 .. k]

fbsArr :: Int -> Int -> Int -> Array Int Int
fbsArr m n k = listArray (0, k) (fbs m n k)

fbm :: Int -> Int -> Int -> Int -> Int
fbm m n k i | (i `div` 2) `mod` 2 == 1 = (xs ! (i - 1)) + (xs ! (i - 2))
            | otherwise                = (xs ! (i - 1)) - (xs ! (i - 4))
  where xs = fbsArr m n k

它与第一个几乎相同,但这次结果在数组中被记忆,并且其元素的索引明显更快。根据我的测试,它为(m, n, k) = (2, 3, 1000)生成的答案比基于列表的方法快10倍。这种情况下的答案是fbsArr m n k ! k

答案 1 :(得分:2)

这个问题类似于“斐波纳契系列”,但在我看来,它们之间存在很大差异。
Memoization 是解决此类问题的常用技术 例如,我们可以用它来计算Fibonacci系列 以下是一个非常简单的说明。它不如zipWith解决方案那么好,但它仍然是一个线性操作实现。

fib :: Int -> Integer
fib 0 = 1
fib 1 = 1
fib n = fibs !! (n-1) + fibs !! (n-2)

fibs :: [Integer]
fibs = map fib [0..]

如果我们尝试模仿上述fibfibs,也许我们会编写以下代码。

fbm :: Int -> Int -> Int -> Int
fbm m n 0 = m
fbm m n 1 = n
fbm m n x = let a = fbs m n !! (x-1)
                b = fbs m n !! (x-2)
                c = fbs m n !! (x-4)
            in case (x `div` 2) `mod` 2 of
                   1 ->  a + b
                   0 ->  a - c

fbs :: Int -> Int -> [Int]
fbs m n = map (fbm m n) [0..]

但上述fbs也非常慢。按数组替换列表几乎没有区别。原因很简单,当我们调用fbs时没有记忆。 如果我们比较fibsfbs的类型签名,答案就会更清晰。

fibs :: [Integer]
fbs :: Int -> Int -> [Int]

其中一个是整数列表,另一个是函数 为了让memoization发生,我们必须以任何方式实现fbs e.g。

fbs m n = let xs = map fbm [0..]
              fbm 0 = m
              fbm 1 = n
              fbm x = let a = xs !! (x-1)
                          b = xs !! (x-2)
                          c = xs !! (x-4)
                      in case (x `div` 2) `mod` 2 of
                             1 ->  a + b
                             0 ->  a - c
          in xs

尾递归是解决此类问题的常用方法。

fbm :: Int -> Int -> Int -> (Int, Int, Int, Int)
-- a[0] = m
-- a[1] = n
-- a[2] = m + n
-- a[3] = m + 2 * n
fbm m n 3 = (m+2*n, m+n, n, m)
fbm m n x = case (x `div` 2) `mod` 2 of
                 1 -> (a+b, a, b, c)
                 0 -> (a-d, a, b, c)
  where (a,b,c,d) = fbm m n (x-1)

最后但并非最不重要的是,这是一个数学解决方案。

a[0] = m
a[1] = n
a[2] = m + n
a[3] = m + 2n
a[4] = 2n
a[5] = n
a[6] = 3n
a[7] = 4n
a[8] = 2n

fbs m n = [m, n, m+n, m+2*n] ++ cycle [2*n, n, 3*n, 4*n]