如何使用带有警卫的尾递归将列表拆分为两个

时间:2018-03-19 07:37:46

标签: haskell tuples list-comprehension

如果我有一个列表可以说[1,2,3,4],我怎么能创建一个两个列表的元组,第一个列表包含奇数元素,第二个列表包含偶数元素。我怎么能这样做尾巴 - 游览? 例如。

    Input  : [1,2,3,4]
    Output : ([1,3],[2,4]) with tail recursion and not ranges. [|x<-...]

到目前为止,我尝试过类似的事情:

sumt::[Int]->([Int],[Int])
sumt []=([],[])
sumt (x:xs)
    | x `mod` 2==0 = x: (fst $ tupl xs)
    | otherwise    = x: (snd $ tupl xs) where 
    tupl []=([],[])
    tupl (y:ys)=y:(tupl ys)   //how can i put the condition here ? I need it 
                            //to be aware of both guard cases at each iteration

我基本上需要两个由每个保护案例组成的本地列表,最后它们放在一个元组中。

2 个答案:

答案 0 :(得分:5)

使用显式递归的最直接的方法是使用带有两个累加器的尾递归辅助函数作为结果列表:

sumt :: [Int] -> ([Int], [Int])
sumt = go [] []
  where

    -- Each recursive call is directly to ‘go’,
    -- so it’s tail-recursive.
    go os es (x:xs)
      | x `mod` 2 == 0 = go os (x:es) xs
      | otherwise = go (x:os) es xs

    -- In the base case, it returns a tuple with
    -- the accumulated results in the proper order.
    go os es [] = (reverse os, reverse es)

更简单的方法是使用partition中的Data.List函数:

sumt :: (Integral a) => [a] -> ([a], [a])
sumt = partition odd

如果你看一下partition的定义,它不是用显式递归实现的,而是用foldr实现的。这里是内联odd

sumt = foldr select ([], [])
  where
    select x ~(os, es)
      | odd x = (x:os, es)
      | otherwise = (os, x:es)

这具有流式传输的优点:它不包括在结束时反转累积列表的O(n)步骤,它只是逐步构建结果。

答案 1 :(得分:3)

尾递归函数是函数的最终结果是回调同一函数的函数。在Haskell中,这意味着等式的右边必须是对函数的调用。所以,例如,    f(x:xs)n = f xs(n + 1) 是尾递归,而    F&#39; (x:xs)= 1 + f&#39; XS 不是 - 因为虽然有递归调用,但它不是函数的结果。相反,(+)的评估是结果。

对于您的代码,这意味着您需要以下内容:

sumt_tr :: [Int] -> ([Int],[Int])
sumt_tr xs = go xs ([],[])
    where
    go [] (os,es) = (reverse os,reverse es)
    go (x:xs) (os,es) | x `mod` 2 == 0 = go xs (os,x:es)
                      | otherwise      = go xs (x:os,es)

这里,函数go(sumt_tr的本地)是尾递归的,因为go的每个等式再次直接调用go。请注意,为了将go写为尾递归,我需要通过将其作为第二个参数传递来累积结果,以便在到达列表末尾时返回。