I have a simple program (it was the second question on CCC 2012) that takes a list of numbers and determines if there is any strictly increasing/decreasing/constant sequence going on. For example:
1 2 3 4 7 8 => Increasing
5 1 -2 -100 => Decreasing
9 9 9 9 9 9 => Constant
1 2 3 4 5 0 => Nothing
I was completely blown away by how smart Haskell was when I coded this. For some reason, when I typed in the numbers interactively into stdin, in was giving me the answer before I had even finished! I thought it was a bug, but then I foolishly realized that Haskell’s laziness (I think?) was taking it upon itself to decide that, after I entered 1, 2, 3, 0, no matter what came after, the result would be Nothing, and so it happily outputted that.
Unfortunately, when I changed
let readings = map (read :: (Read a, Num a) => String -> a) $ lines input
to
let readings = parse $ lines input
with parse being a safer method of reading numerical input, implemented as
maybeRead :: (Read a) => String -> Maybe a
maybeRead = fmap fst . listToMaybe . filter (null . dropWhile isSpace . snd) . reads
parse :: (Read a) => [String] -> [a]
parse xs =
let entries = map maybeRead xs
in if all isJust entries
then map fromJust entries
else []
it no longer does this.
Why?
EDIT: More code
-- | Zip together adjacent list elements as pairs in a new list.
zipPairs :: [a] -> [(a, a)]
zipPairs xs = zip (init xs) (tail xs)
-- | Return True if all elements of a given list are equal.
constant :: (Eq a) => [a] -> Bool
constant xs = all (== head xs) (tail xs)
-- | Return the order that the elements of a list are sorted in, if they form
-- a strictly increasing (Just LT), decreasing (Just GT) or constant (Just EQ)
-- sequence. If there is no pattern, return Nothing.
order :: (Ord a) => [a] -> Maybe Ordering
order xs =
let orders = map (\(x, y) -> x `compare` y) (zipPairs xs)
in if constant orders then Just (head orders) else Nothing
and then in mainI have
let readings = parse $ lines input
putStrLn $ if null readings
then "bad input"
else case order readings of
Just EQ -> "Constant"
Just LT -> "Diving"
Just GT -> "Rising"
Nothing -> "Nothing"
If all entries are justs,
all isJust entrieschecks the entire list of entries, which means that the entire list of entries needs to be read in beforeparsecan return.Okay, longer explanation of why
ordersis lazy —allreturnsFalseas soon as it reaches a value for which the predicate returnsFalse. Therefore,constantreturns false as soon as it hits a value in the tail that isn’t equal to the head.orderreturns as soon asconstantreturns, soorderis lazy.My first suggestion is stylistic — look at the
zipWithfunction when calculatingorders.let orders = zipWith compare xs $ tail xsshould work equally well.As far as solving your actual problem is concerned, try
Note that you need to import
Data.MonadliftM2 comparewill returnJust (compare x y)when passedJust xandJust yandNothingif either or both of its arguments areNothing.ordersis now a[Maybe Ordering]. Ifordersis constant (note:(==)works onMaybes) and the first element is aJust, return the first element (which is already aMaybe Ordering). Otherwise, just returnNothing. You could do without theisJust (head orders)call, but adding it should make it return as soon as it sees aNothing(otherwise, if you give it a list of allNothings, it will check if every one isNothing).