【问题标题】:How to use a tail-recursion with guards for splitting a list into two ones?如何使用带有守卫的尾递归将列表分成两个?
【发布时间】:2018-03-19 07:37:45
【问题描述】:

如果我有一个列表,比如说[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

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

【问题讨论】:

  • 你确定你希望它是尾递归的吗?为什么?此外,您尝试过的代码不是尾递归的。
  • // Comment 不是 Haskell 评论。使用-- Comment
  • 我正在尝试使用守卫和尾递归解决问题。有什么办法可以生成空元组和空列表并使用递归填充它们
  • evenodd 是 Prelude 中的函数。 even 4 将返回 True

标签: haskell tuples list-comprehension


【解决方案1】:

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

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)

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

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) 步骤,它只是逐步构建结果。

【讨论】:

  • 谢谢您的回答先生!
【解决方案2】:

尾递归函数是函数的最终结果是对同一函数的回调。在 Haskell 中,这意味着等式的右边必须是对函数的调用。所以,例如, f (x:xs) n = f xs (n+1) 是尾递归的,而 f' (x:xs) = 1 + f' 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 写为尾递归,我需要通过将结果作为第二个参数传递来累积结果,以便在到达列表末尾时返回。

【讨论】:

  • 好的,为了做到这一点,我必须提供元组的初始值([],[])。我认为我可以先创建列表,然后将它们打包在最后在元组中。类似 -> 遍历初始列表,一次取 1 个元素插入两个局部列表变量之一,然后将它们放入元组中。
  • @BercoviciAdrian 是的,您需要累加器的初始值。当您进行每个尾递归调用时,您通过向累加器添加一个新值来进行累加。在这种情况下,初始值是所谓的“单位”值(即,它充当特定操作的标识)并且该操作是单形的(二进制和关联),您可以将初始值替换为mempty。所以上面代码中对go xs ([],[])的调用可以替换为go xs mempty。顺便说一句,在 Haskell 中阅读和理解幺半群真的很值得一读。
  • 您能否参考一个来源以供进一步阅读?直到现在我都在用我的直觉涉足 Haskell,但我知道在事物的工作原理上有具体的集合论数学,并且可以更好地理解。跨度>
  • @BercoviciAdrian 有很多很好的在线资源。 LYAH(Learn You A Haskell)是一个很好的资源。还有Real World Haskell,它有来自读者的嵌入式 cmets 的好处,但现在有点旧,后面章节中的一些代码有点过时(在这种情况下,阅读 cmets 会有所帮助,因为更新的建议代码已发布)。如果你想花钱,那么Programming Haskell from First Principles绝对是我的最爱。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多