haskell foldr manipulation with lists

亡梦爱人 提交于 2019-12-11 19:10:19

问题


Given a list of sequence of negative and positive numbers, how can I partition them into sequences of negative and positive numbers using foldr?

For example [1,2,3,-1,-2,-3,1,2,3] i will get [[1,2,3],[-1,-2,-3],[1,2,3]]

A few doubts

How do I know that the previous partition that I have already compared if of the same sign as the one I am comparing current?

How do I add the element to the list? I tried something like [x]:y but what I get was each element as a list and concatenated together, which is not the result.

What I have currently is this

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

which is wrong

Many thanks for the help in advance.


回答1:


I second the usage of groupBy instead. However I'd like to raise the point that 0 is not considered a positive number in mathematics. And as no other answers have so far mentioned, anything that is of the Num typeclass must implement signum, which will return the sign of the number given to it.

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

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

Example usage:

> 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]]



回答2:


You want to compare the sign of each number in the list with the sign of its successor. And if the signs are the same, you want to put x in the same list as its successor, otherwise start a new inner list.

So

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

would do what you want (given an implementation of sameSign). But that wouldn't be very efficient (and not work on infinite lists at all), since to know which equation to use, the part after x needs to be constructed and that means the end of the input list must be reached first, then one must step back through the list.

The solution is laziness, you must start constructing the result before inspecting the second argument

combine x wss = (x:ssx) : rest
  where
    (ssx:rest) = case wss of
                   [[]] -> [] : []
                   (yys@(y:ys) : zss)
                       | sameSign x y -> yys : zss
                       | otherwise    -> [] : wss

Then

foldr combine [[]] input

is what you want, with, for example,

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

(of course, using groupBy is shorter and easier, but that doesn't use foldr :)




回答3:


You need a slightly more complex accumulator here:

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]]



回答4:


Use the best tool for the job. In this case, the best tool for the job is groupBy.

groupBy passes two members of the list to your function, so you can easily check whether they have the same sign or not.

(You can write it in terms of foldr if you really want to --- you can write groupBy in terms of foldr, although the standard implementation doesn't --- it just makes things more complicated for you than they need to be.)




回答5:


You could get want you want using e.g. groupBy from Data.List.

import Data.List (groupBy)
let result = groupBy (\ x y -> x*y>0) [1,2,3,-1,-2,-3,1,2,3]

If you do not want to treat 0 separately use e.g. Daniel Fischer's sameSign function instead for the check.




回答6:


I will just provide the possible answer using foldr. I am not saying that you should use this as it is neither very efficient (I am using a*b >=0 to show that a and b are of same sign), nor works on infinite lists.

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


来源:https://stackoverflow.com/questions/12838675/haskell-foldr-manipulation-with-lists

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!