问题
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