Haskell中的递归混淆

时间:2013-08-22 02:03:54

标签: haskell recursion

我希望有人可以帮助弄清楚我的错误所在。调用g 3 4 0 2 (M.empty,0) [],我希望[[2,1,0,1]]成为结果。相反,我看到[[2,1,0,1],[2,1,0,1]]

该程序应该通过每次向列表添加不同的数字来累积长度为m的不同数字模式,在到达n-1时向下返回,在到达0时向上返回。当向上和向下方向递归调用函数时,明显的问题发生在中间。

如果我这样评论第11行:

else g n m (digitCount + 1) (lastDigit + 1) (hash',hashCount') (lastDigit:digits)
  -- g n m (digitCount + 1) (lastDigit - 1) (hash',hashCount') (lastDigit:digits)

我得到了正确的结果[]

当评论第11行并将第10行修改为:

else g n m (digitCount + 1) (lastDigit - 1) (hash',hashCount') (lastDigit:digits)

再次,正确的结果[[2,1,0,1]]

为什么在使用g运算符调用++两次时,我得到两个[2,1,0,1]而不是一个?在我的想法中,g中的每个结果都应该是不同的,因为在任何递归调用中,不同的数字顺序是(或应该)累积。

提前致谢。

import qualified Data.Map as M

g :: Int -> Int -> Int -> Int -> (M.Map Int Bool, Int) -> [Int] -> [[Int]]
g n m digitCount lastDigit (hash,hashCount) digits
  | digitCount == m = if test then [reverse digits] else []
  | otherwise       =
      if lastDigit == 0
         then g n m (digitCount + 1) (lastDigit + 1) (hash',hashCount') (lastDigit:digits)
         else if lastDigit == n - 1
                 then g n m (digitCount + 1) (lastDigit - 1) (hash',hashCount') (lastDigit:digits)
                 else g n m (digitCount + 1) (lastDigit + 1) (hash',hashCount') (lastDigit:digits)
                   ++ g n m (digitCount + 1) (lastDigit - 1) (hash',hashCount') (lastDigit:digits)
 where test = hashCount == n
       (hash',hashCount') = 
         if test
            then (M.empty,hashCount)
            else case M.lookup lastDigit hash of
                   Just anyting -> (hash,hashCount)
                   Nothing      -> (M.insert lastDigit True hash,hashCount + 1)

3 个答案:

答案 0 :(得分:3)

现在你已经有了它,这是一种更通用的方法。

我们需要走解决方案之树。

    data S a = Solution a | Explore [S a]

解决方案是这棵树的叶子,探索是要探索的选项列表。

    -- this is very much unfoldr-like
    generator :: [S a] -> [a]
    generator [] = []
    generator (Solution a: ss) = a: generator ss
    generator (Explore ps: ss) = generator $ ss ++ ps

现在,给出一个“可能解决方案”列表,生成一个解决方案列表。生成器模式匹配Explores,并将要探索的解决方案列表附加到列表的末尾。通过这种方式,我们正在探索广度优先的解决方案,这样我们就可以处理非终止分支。 (深度优先不能离开非终止分支)。这当然是以牺牲内存为代价的,但即使对于无数解决方案的问题,您也可以找到有限数量的解决方案。

现在,为您的问题生成解决方案的函数:

    g :: Int -> Int -> [S [Int]]
    g n m = [Explore $ g' [i] (S.singleton i) | i <- [1..n-1]]  where
      g' is@(h:_) ms
       | h < 0 || h >= n || length is > m = [] --no solution, nothing to explore
       | otherwise = maybeSolution ++ 
                             [ Explore $ g' ((h-1):is) $ S.insert (h-1) ms
                             , Explore $ g' ((h+1):is) $ S.insert (h+1) ms ] 
        where
          maybeSolution
            | S.size ms == n = [Solution is]
            | otherwise      = []

给定n和m,生成一个要探索的子树列表。 g'是生成子树列表的辅助函数,给定已生成的Int列表和已使用的Int集合。因此,有一个明确的终止条件:我们在所需范围之外附加一个数字,或者列表变得太长 - 再探索不能产生解,所以返回[]。否则,我们在界限内,也许解决方案看到Ints列表是否已经是一个有效的解决方案,并建议更多的子树来探索。

    main = print $ map reverse $ generator $ g 3 6

你的问题解决了。

答案 1 :(得分:2)

在最后一个分支中对g(++)的两次递归调用中,除了lastDigit之外,您传递的参数完全相同。

递归的基本情况不会显示lastDigit - 它只是比较mdigitCountnhashCount,然后返回{ {1}}。

因此,在[reverse digits]案例被立即命中,然后返回(++)的基本案例的情况下,您将获得相同的值重复。

我没有完全理解您的问题规范,但您可能需要在进行递归调用时将[reverse digits]的“新”值添加到数字中 - 即lastDigit(lastDigit-1):digits

答案 2 :(得分:2)

  

为什么当使用++运算符调用g两次时,我得到两个[2,1,0,1]而不是仅仅   一?在我的思考中,g中的每个结果都应该是不同的,因为在任何递归调用中,a   不同的数字顺序是(或应该)累积。

但是你的两个(Map,Int)在两个调用中是相同的,所以递归调用不知道另一个调用找到了什么。考虑调用g ...(lastDigit-1)。它也会调用g ...(lastDigit)(通过向它添加1到(lastDigit-1)),并按照分支g ...(lastDigit + 1)产生相同的结果。

另外,(Map a())是一个(Set a),因为你不使用map中的Bool值,它与()相同:

    import qualified Data.Set as S

    g :: Int -> Int -> Int -> Int -> (S.Set Int, Int) -> [Int] -> [[Int]]
    g n m digitCount lastDigit (hash,hashCount) digits
      | digitCount == m = if test then [reverse digits] else []
      | lastDigit < 0 || lastDigit == n  = []
      | otherwise       = g n m d' (lastDigit + 1) h' (lastDigit:digits)
                          ++ g n m d' (lastDigit - 1) h' (lastDigit:digits)
     where test = hashCount == n
           d' = digitCount + 1
           h'
            | test  = (S.empty,hashCount)
            | S.member lastDigit hash  = (hash,hashCount)
            | otherwise = (S.insert lastDigit hash,hashCount + 1)