I write a code to convert a string to be a list of Intger:
convert::String -> Maybe [Int]
convert (c:cs)
isDigit c = Just(c: (convert cs):[])
otherwise = Nothing
and it shows an error...
test.hs:15:26: error:
parse error on input ‘=’
Perhaps you need a 'let' in a 'do' block?
e.g. 'let x = 5' instead of 'x = 5'
Why is that so...
While there are other compile errors in your code, the reason you're getting the error message about the parse error is because you are not including the pipe character used in guards.
convert (c:cs)
| isDigit c = Just(c: (convert cs):[])
| otherwise = Nothing
There were several errors in your code. You need to try to convert each character, which gives a Maybe Int. Then you loop on the string using mapM inside the Maybe monad :
import Data.Char
convertChar :: Char -> Maybe Int
convertChar c = if isDigit c then Just $ ord c else Nothing
convert :: String -> Maybe [Int]
convert = mapM convertChar
Another way of looking at V. Semeria's answer is to use sequence (from the Traversable type class) with map:
import Data.Char -- for ord
convertChar :: Char -> Maybe Int
convertChar c = if isDigit c then Just $ ord c - 48 else Nothing
convert :: String -> Maybe [Int]
-- Start with String -> [Maybe Int], then convert
-- the [Maybe Int] to Maybe [Int]
convert = sequence . map convertChar
This is a common pattern, so Traversable also provides traverse, which in this case is equivalent to sequence . map.
convert :: Traversable t => t Char -> Maybe (t Int)
convert = traverse converChar
Some examples (most of the instances of Traversable available by default aren't very interesting, but various tree types can have instances).
> convert "123"
Just [1,2,3]
> convert (Just '1')
Just (Just 1)
> convert (True, '2')
Just (True, 2)
> convert (Right '1')
Just (Right 1)
Related
I want to write a program that encrypts a text using XOR-cipher. That's what I have for now:
-- XORres 2 strings
stringXor :: String -> String -> String
stringXor s t = map chr $ zipWith xor (map ord s) (map ord t)
-- encryption and decryption
encDec :: String -> String -> String
encDec text key = stringXor (take (length text) (cycle key)) text
But the output of encDec "this is a test" "k" is
"\US\ETX\STX\CANK\STX\CANK\nK\US\SO\CAN\US"
while I was expecting something like 1f0302184b02184b0a4b1f0e181f4b.
What could be the problem here? I've searched similar questions but that wasn't very helpful.
stringXor s t = map chr $ zipWith xor (map ord s) (map ord t)
You map chr to the results of XOR. This results in characters for each ASCII value. To show the results as hexadecimal, you will need to find a different function to replace chr or write one yourself.
Side note: In cryptography, it is common to use Base64 notation to encode binary data instead of hex because it uses less characters (and therefore less memory or network bandwidth) to represent the same binary sequence.
You are getting 1f0302184b02184b0a4b1f0e181f4b:
> "\x1f\x03\x02\x18\x4b\x02\x18\x4b\x0a\x4b\x1f\x0e\x18\x1f\x4b"
"\US\ETX\STX\CANK\STX\CANK\nK\US\SO\CAN\USK"
...which is exactly the output you saw from encDec (up to what appears to be a simple copy-paste error in your expected output).
The problem is with the chr :: Int -> Char. This function converts an Int to the corresponding character, but not a hexadecimal representation of that number.
You can for example define a utility function with intToDigit :: Int -> Char:
import Data.Char(intToDigit)
toHex2 :: Int -> String
toHex2 h = map intToDigit [d, m]
where (d, m) = divMod h 16
Then we can implement the function as:
stringXor :: String -> String -> String
stringXor s t = concatMap toHex2 (zipWith xor (map ord s) (map ord t))
Or as #DanielWagner says:
import Data.Function(on)
stringXor :: String -> String -> String
stringXor s t = concatMap toHex2 (zipWith (xor `on` ord) s t)
This then gives us:
Prelude Data.Char Data.Bits> encDec "this is a test" "k"
"1f0302184b02184b0a4b1f0e181f"
Note that you do not need to use length here, in fact it is safer without length, you can just use cycle:
encDec :: String -> String -> String
encDec text key = stringXor (cycle key) text
I want to write a function that gets the first integer in a string, if the integer is at the beginning of the string and is followed by space or nothing.
For example, "12" and "12 kids" would be valid strings, but "12kids", "a12" are invalid.
This is my function:
getFirstInteger :: String -> Int
getFirstInteger [] = error "No integer"
getFirstInteger str
| dropWhile (Char.isNumber) str == [] = read str :: Int
| Char.isSpace $ head $ dropWhile (Char.isNumber) str = read (takeWhile (Char.isNumber) str) :: Int
| otherwise = error "No integer found"
The problem is that if the string is actually an integer, head will raise an exception, so that is why the first condition exists. Is there any elegant solution to this problem?
getFirstInteger :: String -> Int
getFirstInteger = read . head . words
words will split the String into a list of Strings, which were originally separated by whitespace. head will take the first (or error if the original string was empty), and read will parse the string as usual (and error if the first word wasn't a valid Int).
However, I prefer a variant that doesn't use error on empty Strings or unparseable ones, e.g.
import Text.Read (readMaybe)
getFirstInteger :: String -> Maybe Int
getFirstInteger [] = Nothing
getFirstInteger xs = readMaybe . head . words $ xs
One could write this completely point-free with listToMaybe from Data.Maybe, but that's probably an overkill:
import Data.Maybe (listToMaybe)
import Text.Read (readMaybe)
import Control.Monad ((>=>))
getFirstInteger :: String -> Maybe Int
getFirstInteger = listToMaybe . words >=> readMaybe
If you want to parse strings without using parser combinator libraries or any of the machinery in Text.Read, have a look at the functions break and span:
span :: (a -> Bool) -> [a] -> ([a], [a])
break :: (a -> Bool) -> [a] -> ([a], [a])
The nice thing is that both of these functions not only return what they match but also the remainder of the string allowing you to continue your parsing.
To solve your problem:
import Data.Char
getFirstInteger :: String -> Maybe Int
getFirstInteger str
let (digs, rest1) = span isDigit str
endsok = case rest1 of
[] -> True
(c:_) -> c == ' '
in
if not (null digs) && endsok
then Just (read digs)
else Nothing -- either no digits or doesn't end properly
This version does not allow a leading minus sign. This next version allows an optional leading minus sign for the integer:
getFirstInteger' str =
let (minus,rest1) = span (=='-') str
(digs, rest2) = span isDigit rest1
endsok = case rest2 of
[] -> True
(c:_) -> c == ' '
in
if length minus <= 1 && not (null digs) && endsok
then Just (read (minus ++ digs))
else Nothing
Yes - this does not terminate as early as possible on bad input. I provide it mainly as an example of how to chain together calls to span and break.
Use reads. For example:
type Unit = String
readUnit :: String -> Maybe (Int, Maybe Unit)
readUnit s = case reads s of -- the integer is at the beginning of the string and...
(n, ' ':unit):_ -> Just (n, Just unit) -- is followed by space...
(n, "" ):_ -> Just (n, Nothing) -- or nothing.
_ -> Nothing
In ghci:
> readUnit "12"
Just (12,Nothing)
> readUnit "12 kids"
Just (12,Just "kids")
> readUnit "12kids"
Nothing
> readUnit "a12"
Nothing
However, there are a few minor considerations to keep in mind. It's possible that read does not restrict the syntax as much as you might want; for example, the following answer may surprise you:
> readUnit " ((-0x5)) kids"
Just (-5,Just "kids")
You may also want to drop extraneous spaces in the unit; for example, you could change the first clause above to
(n, ' ':unit):_ -> Just (n, Just (dropWhile isSpace unit))
or similar. And as a final variation on this theme, note that while the standard instances of Read never return lists with more than one element from reads, it is technically possible that some user-supplied type may do so. So if you were ever to use reads to parse types other than Int, you may want to either demand an unambiguous parse or consider all the parses before deciding what to do; the above code bakes in the assumption that the first parse is as good as any.
Im working through the exercises on wikibooks/haskell and there is an exercise in the MonadPlus-chapter that wants you to write this hexChar function. My function works as shown below, but the thing is that when I try to switch the 2 helper parsers (digitParse and alphaParse) around the function ceases to work properly. If I switch them around I can only parse digits and not alphabetic chars anymore.
Why is this so?
char :: Char -> String -> Maybe (Char, String)
char c s = do
let (c':s') = s
if c == c' then Just (c, s') else Nothing
digit :: Int -> String -> Maybe Int
digit i s | i > 9 || i < 0 = Nothing
| otherwise = do
let (c:_) = s
if read [c] == i then Just i else Nothing
hexChar :: String -> Maybe (Char, String)
hexChar s = alphaParse s `mplus` digitParse s -- cannot switch these to parsers around!!
where alphaParse s = msum $ map ($ s) (map char (['a'..'f'] ++ ['A'..'F']))
digitParse s = do let (c':s') = s
x <- msum $ map ($ s) (map digit [0..9])
return (intToDigit x, s')
if read [c] == i then Just i else Nothing
The marked code has a flaw. You're using Int's Read instance, e.g. read :: String -> Int. But if it's not possible to parse [c] as an int (e.g. "a"), read will throw an exception:
> digit 1 "doesnt start with a digit"
*** Exception: Prelude.read: no parse
> -- other example
> (read :: String -> Int) "a"
*** Exception: Prelude.read: no parse
Instead, go the other way:
if [c] == show i then Just i else Nothing
This will always works, since show won't fail (not counting cases where bottom is involved).
I have the code which create Data.Map:
import qualified Data.Map as Map
shift_string :: [Char] -> Int -> [Char]
shift_string s num = (drop num s) ++ (take num s)
ascii :: [Char]
ascii = ['a' .. 'z']
shifted_ascii :: Int -> [Char]
shifted_ascii n = shift_string ascii n
trans_table :: Int -> Map.Map Char Char
trans_table n = Map.fromList(zip ascii $ shifted_ascii n)
The 'trans_table' function returns the Map where one Char map to another map.
I can create the function to get one Char and return another one based on this Map:
translate_char :: Char -> Int -> Maybe Char
translate_char c n = Map.lookup c $ trans_table n
Now I want to 'translate' each symbol in the map. Something like this:
encode message key = map translate_char message
This code doesn't work as translate_function must have only one parameter. I need something like global variable to store the Map in it and lookup values from it in my map-function. But I don't know how to rewrite my code.
p.s. I guess I can just add 'key' to each char in my string but I am searching common solution.
I don't know whats key in your encode function but you can use something like
translate_char :: Int -> Char -> Maybe Char
translate_char n c = Map.lookup c $ trans_table n
encode :: Int -> String -> String
encode n s = catMaybes $ map (translate_char n) s
here n determines the number of characters you are rotating. I switched the order of parameters to avoid using flip.
flattern :: [(Char, Int)] -> String
flattern [] = ""
flattern ((w,l):xs) = show l ++ w : flattern xs
What would be the inverse function of this? Is there any way of be able to work this out?
It's not invertible:
There are strings that cannot be reproduced by this function (any string not starting with a digit).
It's not even partially invertible. There are also strings that correspond to multiple inputs: "1111" can be produced by either [('1',1),('1',1)] or [('1',111)].
Are you sure that this was the function to invert, and not something like flattern ((w,l):xs) = replicate l w ++ flattern xs?
If you really want, you could parse the output of the function to try and reconstruct what the arguments must have been.
import Text.ParserCombinators.Parsec
unflat1 :: Parser (Char, Int)
unflat1 = do
c <- anyChar
n <- many1 digit
return (c, read n)
readExpr :: String -> Either String [(Char, Int)]
readExpr input = case parse (many unflat1) "unflat" input of
Left err -> Left ("No match: " ++ show err)
Right val -> Right val
This sorta works, as long as flattern does not have numbers as the first input.
Something like flattern [('a',1), ('2',3)] will be parsed as [(a, 123)].