TabbiCat
TabbiCat

Reputation: 21

Haskell converting string to binary number

I need to convert a string of chars to a list of binary numbers in Haskell. I've written two functions to do this, but I'm not sure how to combine them into one. So far I have

dec[]=[]
dec(x:xs) = ord(x): dec xs

to convert every char in the list into a decimal number. The next function

bin 0 = [0]
bin n| n `mod` 2 == 1 = bin (n `div` 2) ++ [1]
     | n `mod` 2 == 0 = bin (n `div` 2) ++ [0]

converts a decimal number to its binary equivalent. I'm not sure how to apply the second function to every element in the list, in order to convert every char to its equivalent in binary. I tried to use the where clause:

where n = dec(x:xs) = ord(x): dec xs

but this is not valid as there are two equals signs on the same line. How can I achieve the correct functionality?

Upvotes: 2

Views: 2440

Answers (2)

pat
pat

Reputation: 12749

You can be pretty certain that an Int will be stored in binary. It only appears to be in decimal because it is converted to decimal when you print it. So, the name dec is a misnomer, that function is converting a String into a sequence of numbers that represent the Unicode value of each character. You can avoid explicit recursion by using map:

toUnicode :: String -> [Int]
toUnicode = map ord

Note that this function uses so-called point-free style. The expected argument is missing, but will be passed to map when supplied by the caller.

The Bin function will not compile because it starts with an upper case character, making it a data constructor. You should name the function starting with a lower case character. According to your example output, you want leading zeros in your binary representations, so you can't stop conversion when the value becomes zero. You need to continue until you have converted the desired number of digits, which appears to be 8. It is also inefficient to keep appending to a list. It is better to prepend, and then reverse the result.

toBinary :: Int -> [Int]
toBinary = go 8 [] where
    go 0 acc _ = reverse acc
    go n acc x = go (n-1) (bit:acc) x' where
        (x', bit) = x `divMod` 2

Here, we use a helper function, go which counts down the number of remaining digits as it builds up the list of 1's and 0's.

So, now we have a function to convert a String into a list of Ints, and a function to convert an Int into a list of 0/1 Ints, and we want to glue them together to make a function that converts a String to a list of 0/1 Ints. If we map our toBinary function over the result of toUnicode, we will get a list of lists, which must be concatenated to form a single list. This is such a common pattern that there's a function for that called, concatMap:

stringToBinary :: String -> [Int]
stringToBinary = concatMap toBinary . toUnicode

Here we use function composition to first apply toUnicode to the String, and then concatMap the toBinary over the result.

Upvotes: 1

delta
delta

Reputation: 3818

What we want is a function of type String -> String (decimal -> binary). What you have now is

dec :: String -> [Int]
bin :: Int -> [Int] -- use *lowercase*

So it seems impossible to compose a function of type String -> String only with these two. Besides, ord is not what you want.

*Main> dec "123"
[49,50,51]
*Main> bin 123
[0,1,1,1,1,0,1,1]

From what you have now, the possible solution would be:

*Main Data.Char> toBinary = map intToDigit . bin . read
*Main Data.Char> toBinary "123"
"01111011"

I guess your intention may be dec :: String -> Int, then bin . dec :: String -> [Int]. You can follow the type signature and retry.

Upvotes: -1

Related Questions