带列表的haskell foldr操作

时间:2012-10-11 11:39:11

标签: haskell fold

给出一个负数和正数序列表,如何使用foldr将它们分为负数和正数序列?

例如[1,2,3,-1,-2,-3,1,2,3]我会得到[[1,2,3],[ - 1,-2,-3], [1,2,3]]

有些疑惑

我如何知道我之前已经比较的分区是否与我正在比较当前分区的符号相同?

如何将元素添加到列表中?我试过类似[x]的东西:但我得到的是每个元素作为一个列表并连接在一起,这不是结果。

我目前所拥有的是

foldr (\ x y -> if  x >= 0  then [x]:y else y ) [[]] 

这是错误的

非常感谢您提前获得的帮助。

6 个答案:

答案 0 :(得分:5)

我改为使用groupBy。但是我想提出一个观点,即0在数学中不被认为是正数。至于到目前为止还没有提到其他答案,Num类型类的任何内容都必须实现signum,这将返回给定数字的符号。

import Data.List     (groupBy)
import Data.Function (on) -- Can evade a lambda

signGroup :: (Num a) => [a] -> [[a]]
signGroup = groupBy ((==) `on` signum)

使用示例:

> signGroup [1,2,3,0,0,-1,-2,1,2,0,3,4,-1]
[[1,2,3],[0,0],[-1,-2],[1,2],[0],[3,4],[-1]]

答案 1 :(得分:2)

您希望将列表中每个数字的符号与其后继符号进行比较。如果符号相同,您希望将x放在与其后继者相同的列表中,否则启动一个新的内部列表。

所以

combine x [[]] = [[x]]
combine x wss@(yys@(y:_):zss)
  | sameSign x y = (x:yys) : zss
  | otherwise    = [x] : wss

会做你想要的(给定sameSign的实现)。但这不会非常有效(并且根本无法在无限列表上工作),因为要知道要使用哪个等式,需要构造x之后的部分,这意味着输入列表的结尾必须是首先到达,然后必须退一步。

解决方案是懒惰,你必须在检查第二个参数

之前开始构造结果
combine x wss = (x:ssx) : rest
  where
    (ssx:rest) = case wss of
                   [[]] -> [] : []
                   (yys@(y:ys) : zss)
                       | sameSign x y -> yys : zss
                       | otherwise    -> [] : wss

然后

foldr combine [[]] input

就是你想要的,例如,

sameSign x y
    | x < 0     = y < 0
    | otherwise = y >= 0

(当然,使用groupBy更短更容易,但不使用foldr:)

答案 2 :(得分:1)

你需要一个稍微复杂的累加器:

data Sign = Neg | Zero | Pos

signGroup :: [Integer] -> [[Integer]]
signGroup xs = case
  foldr
  (\x (sign, ps, ns, ys) ->
    -- x    - current element
    -- sign - sign of the prev. group
    -- ps   - positive numbers in the current group
    -- ns   - negative numbers in the current group
    -- ys   - final list
    if x >= 0
    then case sign of
      Neg  -> (Pos, x : ps, [], ns : ys)
      Zero -> (Pos, x : ps, [], ys)
      Pos  -> (Pos, x : ps, [], ys)
    else case sign of
      Neg  -> (Neg, [], x : ns, ys)
      Zero -> (Neg, [], x : ns, ys)
      Pos  -> (Neg, [], x : ns, ps : ys))
  (Zero, [], [], [])
  xs
 of
    (_, [], [], ys) -> ys
    (_, [], ns, ys) -> ns : ys
    (_, ps, [], ys) -> ps : ys
    (_, ps, ns, ys) -> ps : ns : ys -- <- unreachable

-- signGroup [1,2,3,-1,-2,-3,1,2,3]
-- => [[1,2,3],[-1,-2,-3],[1,2,3]]

答案 3 :(得分:0)

使用最佳工具完成工作。在这种情况下,工作的最佳工具是groupBy

groupBy将列表中的两个成员传递给您的函数,因此您可以轻松检查它们是否具有相同的符号。

(如果你真的想用foldr,你可以用groupBy来编写foldr {{1}},尽管标准实现没有 - - 它让你的事情变得比他们需要的更复杂。)

答案 4 :(得分:0)

您可能希望使用例如来自groupBy的{​​{1}}。

Data.List

如果您不想单独处理import Data.List (groupBy) let result = groupBy (\ x y -> x*y>0) [1,2,3,-1,-2,-3,1,2,3] ,请使用例如Daniel Fischer的0函数用于检查。

答案 5 :(得分:0)

我将使用foldr提供可能的答案。我并不是说你应该使用它,因为它不是非常有效(我使用* b&gt; = 0来表示a和b具有相同的符号),也不适用于无限列表。

combine = foldr f []
    where
        f a [] = [[a]]
        f a rest@((x:xs):ys) | x*a >= 0 = (a:x:xs):ys
                             | otherwise = [a]:rest