I need a program that receives a String containing an octal number and converts it to decimal. If the String contains anything that's not a number from 0 to 8, the function should return a 0.
This is what I got:
octoDec :: [Char] -> Int
octoDec [] = 0
octoDec (x:xs) = ((digitToInt(x)) * 8 ^(length xs)) octoDec xs
If I enter octoDec ['1','2','3']
I get 83
, which is expected. However, how can I validate the user's without needing another function?
Edit: I've manage to build a function that checks if a number contains only digits between 0 and 7:
isOcto :: [Char] -> Bool
isOcto [] = True
isOcto (x:xs) | (digitToInt(x) > 0) && digitToInt(x) < 7 = isOcto (xs)
|otherwise = False
what i wanted is to merge these two functions into one and return zero to invalid.
CodePudding user response:
If you want octoDec
to not only return the result, but also determine whether a result is even possible, return a Maybe Int
instead of Int
:
octoDec :: [Char] -> Maybe Int
octoDec [] = Just 0
octoDec (x:xs) = do
rest <- octoDec xs
let d = digitToInt x
guard $ d >= 0 && d <= 7
pure $ rest d * 8^length xs
The guard
function from Control.Monad
will make the whole do
block return Nothing
if the condition doesn't hold.
CodePudding user response:
First, you'll want to use Horner's Method to efficiently convert a sequence of digits to a single value.
> foldl (\acc n -> 8*acc n) 0 (map digitToInt "123")
83
map digitToInt
parses the string, and
foldl (\acc n -> 8*acc n) 0
is a function that evaluates the result of the parse.
horner :: [Int] -> Int
horner = foldl (\acc n -> 8*acc n) 0
parseString :: [Char] -> [Int]
parseString = fmap digitToInt
octoDec :: [Char] -> Int
octoDec = horner . parseString
However, digitToInt
isn't quite right: it can accept digits greater than 7,
> parseString "193"
[1,9,3]
and raises an exception for a value that isn't a digit at all.
> parseString "foo"
[15,*** Exception: Char.digitToInt: not a digit 'o'
We can write a better a function:
octalDigitToInt :: Char -> Maybe Int
octalDigitToInt c | c `elem` "01234567" = Just (digitToInt c)
| otherwise = Nothing
We can use that to convert an octal number into a sequence of digits, using traverse
instead of fmap
:
parseString' :: [Char] -> Maybe [Int]
parseString' = traverse octalDigitToInt
Valid octal string produce a Just
value:
> parseString' "123"
Just [1,2,3]
while invalid strings produce a Nothing
value:
> parseString' "193"
Nothing
> parseString' "foo"
Nothing
(Think of traverse
as being a function that not only applies a function to a list of values, but only produces a list of results if each application succeeds. More precisely, it's a combination of sequence
and fmap
:
traverse f = sequence . fmap f
where sequence
is the function that "inverts" a value of type [Maybe Int]
to a value of type Maybe [Int]
.)
With a more robust version of parseString
, we need to adapt octoDec
to handle the possibility that parsing will fail. We do that by using fmap
to "lift" horner
into the Maybe
functor.
octoDec' :: [Char] -> Maybe Int
octoDec' s = fmap horner (parseString' s) -- or octoDec = fmap horner . parseString'