Reputation: 1159
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
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
Reputation: 67467
perhaps easier this way
checkBoolean xs | and xs = Just_True
| (not . or) xs = Just_False
| otherwise = Both
Upvotes: 3
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
Reputation: 2300
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
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
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