Home > OS >  how to check if a number is in octal in Haskell
how to check if a number is in octal in Haskell

Time:11-12

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'
  • Related