cheshire
cheshire

Reputation: 1159

Checking if all bolean elements in a list are the same in Haskell

I wanna check if boolean elements in a list are all True, all False, or both True and False. Here is my code for now:

data Answer3 = Just_True | Just_False | Both deriving (Eq, Ord, Show)
type A3 = Answer3


checkBoolean :: [Bool] -> A3
checkBoolean (x:x1:xs) = if (length (x:x1:xs) `mod` 2) == 0  
                            then if (x && x1) && checkBoolean'(x1:xs)== True
                                     then Just_True
                                     else if (x && x1) == True
                                             then Both
                                             else if checkBoolean'(xs)== True
                                                     then Both
                                                     else Just_False


                            else if x && checkBoolean'(x1:xs) == True
                                    then Just_True
                                    else if x == False
                                            then Just_False
                                            else Both 

checkBoolean':: [Bool] -> Bool
checkBoolean' (x:xs) = x && (checkBoolean'(xs))
checkBoolean' [] = True

Calling

*Main> checkBoolean [False, False, False]
Just_False

or

*Main> checkBoolean [True, True, True]
Just_True

or

*Main> checkBoolean [True, False, False]
Both

gives correct result. But

*Main> checkBoolean [False, False, True, False, True]
Just_False

and

*Main> checkBoolean [False, False, True]
Just_False

do not work as I intended. [False, False, True, False, True] is Both, and [False, False, True] is also Both

I know I didn't think all possible cases and thats why this doesn't work, but is there an efficient way to write this without writing this much if else statements?

Upvotes: 10

Views: 12446

Answers (6)

Bartek Banachewicz
Bartek Banachewicz

Reputation: 39370

Oh wow, you kinda overcomplicated it.

There's a very useful function called all:

all :: (a -> Bool) -> [a] -> Bool

It checks all elements for a predicate, which for booleans could be just a pair of id and not functions, but you can also explicitly check equality to a value, which makes for pretty readable code:

checkBoolean xs = 
    if all (== True) xs then Just_True
    else if all (== False) xs then Just_False
    else Both

It's probably also a good idea to be somewhat more explicit about the empty list ([]) case. Should it be Just_True, Just_False, Both, or perhaps another value altogether?

Upvotes: 12

karakfa
karakfa

Reputation: 67467

perhaps easier this way

checkBoolean xs | and xs        = Just_True
                | (not . or) xs = Just_False
                | otherwise     = Both

Upvotes: 3

chepner
chepner

Reputation: 530843

You're missing a case to handle empty lists:

-- checkBoolean [] = Neither
data Answer4 = Just_True | Just_False | Both | Neither

Answer4 has a simple, if long-winded, Semigroup instance.

instance Semigroup Answer4 where
    -- like combines with like
    Just_True <> Just_True = Just_True
    Just_False <> Just_False = Just_False
    -- Neither is "like" anything
    x <> Neither = x
    Neither <> x  = x
    -- otherwise, you get Both
    _ <> _ = Both

and a simple Monoid instance:

instance Monoid Answer4 where
    mempty = Neither

Now, if you have a way of converting a Bool to an Answer4

lift :: Bool -> Answer4
lift True = Just_True
lift False = Just_False

you can reduce a list of Bool to a single Answer4 value quickly:

-- foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
-- Here, t ~ [], a ~= Bool, and  m ~ Answer4, for a specialized type
-- foldMap :: (Bool -> Answer4) -> [Bool] -> Answer4
checkBoolean :: [Bool] -> Answer4
checkBoolean = foldMap lift

Upvotes: 6

If you want to use the pre-available functions on lists, you should consider,

partition :: (a -> Bool) -> [a] -> ([a], [a])  

available in Data.List. Using this function you can partition your list into a pair (ts,fs) where ts and fs are respectively the list with all the True and all the False elements of the original list. Then you just have to check if neither is empty, or if only one of them is and which. You can also decide what to return in the case where both are empty.

Note that with this implementation, the traversal of the list stops as soon as both a True and a False element are found. Because that's enough to know neither list in the partition pair is empty. This is a desirable property for a solution of this problem, i.e. once you have gathered enough information to know the answer, you should stop and return the result.


At this point though, considering your own attempt at solving the problem, I'd suggest you should perhaps be focusing on building your own recursive solution.

Upvotes: 3

Dietrich Epp
Dietrich Epp

Reputation: 213258

The functions all or any come to mind first:

any :: Foldable t => (a -> Bool) -> t a -> Bool
all :: Foldable t => (a -> Bool) -> t a -> Bool

If we specialize for lists, we replace t with [], which makes this looks like:

any :: (a -> Bool) -> [a] -> Bool
all :: (a -> Bool) -> [a] -> Bool

We can compare all of the elements to the first element to see if the list contains all the same value. If any elements are different, then we know that the list is mixed.

checkBoolean (x:xs)
      -- Check if any elements are not equal to the first one.
    | any (/= x) xs = Both
    | x             = Just_True
    | otherwise     = Just_False

We might want to handle the degenerate case as well:

-- "Just_True" is arbitrary... you could say "Just_False", too.
checkBoolean [] = Just_True

It is worth noting that as an exercise, you find it valuable to write the function recursively, without the use of higher-order functions like all or any, even though they are in the prelude. Here is an alternative, tail-recursive implementation:

checkBoolean [] = Just_True
checkBoolean (x:xs) = check xs
  where
    check [] = if x
               then Just_True
               else Just_False
    check (y:ys) = if x == y
                   then check ys
                   else Both

I would not expect to see this longer version in actual project code.

Upvotes: 12

lambda.xy.x
lambda.xy.x

Reputation: 4998

The pattern matching can probably integrated into checkBoolean but the main observation is that we don't need check all elements twice: we check for membership of True and False anyway, so from not (elem True xs) we can conclude that the list is either empty or only contains False. For that I'll extend Answer3 by the None case.

data Answer3 = Just_True | Just_False | Both | None deriving (Eq, Ord, Show)

cat True  True  = Both
cat True  False = Just_True
cat False True  = Just_False
cat False False = None

checkBoolean xs = cat (elem True xs) (elem False xs)

Upvotes: 2

Related Questions