Haskell: change string to whole lowercase alpha string using list comprehension

I find that list comprehension is nearly impossible compared to recursion. I am trying to take a string like "te1234ST" and return "TEST". Sounds easy, but there are limitations. It is not allowed to use any of the predefined Haskell functions such as isAlpha, and this MUST be in a list comprehension.

What I have so far, which is pretty awful, how long have I spent on this:

    convertAllToUpper :: String -> String
    convertAllToUpper xs = [n |n <- xs, check n == True]

          -- This may not even be allowed, and I know it incorrect anyway
    check :: n -> Bool
    check (n:ns)
        | n `elem` ['a'..'z']       = True
        | n `elem` ['A'..'Z']       = True
        | otherwise         = False

      

I'm just trying to get this to work and I haven't even started to worry about replacing a lowercase letter with an uppercase one.

Any points in the right direction would be much appreciated.

EDIT: It should be mentioned that converting from bottom to top cannot use: if, then else. Just list the comprehension and list of operators.

+3


source to share


4 answers


Your problem can be broken down into two sub-tasks:

  • Select alphabetic characters only (characters between 'a' and 'z', or 'A' and 'Z')
  • Convert lowercase letters to uppercase.

The first can be done using a filter or (in the sense of a list) a condition for the selected item. In Unicode (and ASCII), lowercase characters appear after uppercase characters, so we can trivially just check if a character is less than "a" to determine if it is uppercase (if we know a letter), and all alphabetic characters in English -arifet, so, for example, a lowercase letter is the letter "a" and "z" (inclusive).

With Data.Char (chr, ord):

f xs = [ if x < 'a' then x else chr $ ord x + ord 'A' - ord 'a'
         | x <- xs, (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') ]

      

With only prelude (but better write using Data.Map):

f xs = [ if x < 'a' then x else maybe x id $ lookup x charMap
         | x <- xs, (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') ]
  where charMap = zip ['a' .. 'z'] ['A' .. 'Z']

      

The correct way, of course, is to use the standard library. This can be done trivially with some rudimentary functions:



-- with Data.Char (toUpper, isAlpha)
f xs = [ toUpper x | x <- xs, isAlpha x ]

      

This is vastly superior in many ways: it's probably faster, and it doesn't rely on ASCII input - it can handle any Unicode character (and basically any localization: for example, Turkish "i" is correct capitalized "like", İ, and not "I, as it would be in ASCII or English, since" I am the capital ", although I don't know if the implementation of Haskell is correctly implemented).

Note that list comprehension is a subset of recursion: if you can write a recursive function of the form:

f []       = []
f (x : xs) = if p x then g x : f xs else f xs 

      

it can be mechanically converted to a shape comprehension list:

f xs = [ g x | x <- xs, p x ]

      

although you can also have multi-variable expressions that are a little harder to express recursively. Therefore, if you understand recursion, comprehension of lists should be trivial for you.

+9


source


It's hard to avoid the predefined functions all the way - I suppose using the list functions defined in the prelude is okay? Everything here is thrown into list comprehension.

upcaseLetters :: String -> String
upcaseLetters cs =
  [d | c <- cs
     , c `elem` (['a'..'z'] ++ ['A'..'Z']) -- test if `c` is a latin letter.
     , let d = if c `elem` ['A'..'Z']      -- test if `c` is uppercase
               then c
               -- Index into [A..Z] using the index of `c` in [a..z]
               else ['A'..'Z'] !! head [i | (i, x) <- zip [0..] ['a'..'z']
                                          , x == c]]

      

However, you may feel that using these list functions is changing. And real programmers avoid any external dependencies. By following this philosophy, we can download a good portion of the foreplay in our list comprehension:

upcaseLetters :: String -> String
upcaseLetters cs =
  [toUpper' c | c <- cs
     , let foldr' _ z []     =  z
           foldr' f z (x:xs) =  f x (foldr' f z xs)

           True ||| _ = True
           _ ||| True = True
           _ ||| _    = False

           elem' e xs = foldr' (|||) False [e==x | x <- xs]

           head' (x:_) = x

           zip' (a:as) (b:bs) =  (a, b) : zip' as bs
           zip' _ _           =  []

           isAlpha' x = x `elem'` (['a'..'z'] ++ ['A'..'Z'])

           isUpper' x = x `elem'` ['A'..'Z']

           toUpper' e
             | isUpper' e = e
             | otherwise  = ['A'..'Z'] !! head' [i | (i, x) <- zip' [0..] ['a'..'z']
                                                   , x == e]
     , isAlpha' c
     ]

      



This approach combines the clarity of folding with the readability of comprehension of lists.

Unfortunately, due to an oversight in language design, Haskell cannot declare new data types in the body of a list comprehension. This means that we cannot clear ourselves of the dependencies on the prelude Char, String and Bool types.

Otherwise, [toUpper x | x <- xs , isAlpha x]

this is what you would normally like.

+1


source


Take a look at this

-- The char you want to take into account
valid_char = ['a'..'z'] ++ ['A'..'Z']

-- To filter the other char
valid xs = [ x | x<- xs, v <- valid_char, x==v]

-- Transform the list of valid char in a list of valid upper char 
to_upper xs = [if (x==fst n) then snd n else x | x <- xs, n <- (zip ['a'..'z'] ['A'..'Z']), (x==fst n) || (x==snd n)]  

-- composition of the two preceding function
convert = to_upper . valid

      

And the test

$ convert "test1234ST" => "TEST"

      

+1


source


convertAllToUpper :: String -> String
--                                  This is equivalent to `check n`
convertAllToUpper xs = [n |n <- xs, check n == True]

-- This is a type declaration. Types always begin with a uppercase letter.
-- Therefore "n" can't be valid in this position.
-- You want "check :: Char -> Bool"
check :: n -> Bool
-- This is a pattern match for a list, but you call it with single
-- characters. That can't work.
check (n:ns)
    -- Again, this is the "if foo then true else false" pattern.
    -- This is redundant.
    | n `elem` ['a'..'z']       = True
    | n `elem` ['A'..'Z']       = True
    | otherwise         = False
-- A shorter version of check
check' :: Char -> Bool
check' x = x `elem` ['a'..'z'] ++ ['A'..'Z']

-- when you have written the function `toUpper`
-- (or taken it from Data.Char`) you can simply apply it to
-- every element in the comprehension.
convertAllToUpper' xs = [toUpper n | n <- xs, check n]

      

0


source







All Articles