Haskell: 利用列表推导将字符串转换为全大写字母字符串

3
我将列表推导式与递归相比较,发现前者几乎不可能实现。我试图将一个字符串“te1234ST”转换为“TEST”。看起来很简单,但有一些限制:不能使用任何Haskell预定义函数,如isAlpha,并且必须使用列表推导式。
目前为止,我已经花费了很长时间,但是我的代码还很糟糕:
    convertAllToUpper :: String -> String
    convertAllToUpper xs = [n |n <- xs, check n == True]

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

我只是在尝试让这个工作起来,甚至还没有开始担心如何将小写字母改为大写字母。

任何指向正确方向的建议都会非常非常感谢。

编辑:应该提到,不能使用if、then、else进行从小写转换为大写的转换。只能使用列表推导式和列表运算符。


1
你可以编写自己的函数将小写字母转换为大写字母,然后调用该函数吗?还是必须在列表推导式中完成所有操作? - Jeff Burka
我相信这一切都可以通过列表推导完成...但是如果需要,我也可以编写一个递归函数将小写字母转换为大写字母。 - john stamos
4个回答

9
你的问题可以分解为两个子问题:
  1. 选择只有字母的字符(即'a'到'z'或'A'到'Z'之间的字符)
  2. 将小写字母转换为大写字母。
前者可以使用过滤器或(在列表推导式中)所选元素的条件来完成。在Unicode(和ASCII)中,小写字母位于大写字母之后,因此我们可以轻松地通过检查字符是否小于'a'来确定它是否是大写字母(一旦我们知道它是一个字母),所有字母字符都按照英语字母表的顺序排列,例如小写字母是介于'a'和'z'之间(包括二者)的字母。
使用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') ]

只使用Prelude(但最好使用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']

当然,正确的方法是使用标准库。这可以通过一些基本函数轻松完成:
-- with Data.Char (toUpper, isAlpha)
f xs = [ toUpper x | x <- xs, isAlpha x ]

这种方法在许多方面都更加优越:它可能更快,并且不依赖于ASCII输入 - 它可以处理任何Unicode字符(原则上可以处理任何本地化:例如,土耳其语的'i'正确大写为'İ',而不是像ASCII或英语环境中那样为'I',因为'I'是'ı'的大写字母,尽管我不知道任何Haskell实现是否正确实现了这一点)。
请注意,列表推导式是递归的一个子集:如果您可以编写一个形式为以下形式的递归函数:
f []       = []
f (x : xs) = if p x then g x : f xs else f xs 

它可以被机械地转换成一个形式为列表推导的表达式:

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

虽然您也可以使用多变量列表表达式,但在递归中更难表达。因此,如果您理解递归,那么列表推导应该对您来说很容易。


1

避免一直使用预定义函数很难 - 我认为使用预定义的列表函数是可以的吗?这里将所有东西都放入了列表推导中。

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]]

然而,您可能会觉得使用这些列表函数是作弊的。真正的程序员会避免任何外部依赖。遵循这个哲学,我们可以在列表推导中引导出大部分prelude:

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
     ]

这种方法结合了折叠的清晰性和列表推导式的可读性。
不幸的是,由于语言设计上的疏忽,Haskell无法在列表推导式的主体中声明新的数据类型。这意味着我们不能摆脱对prelude中的Char、String和Bool类型的依赖。
否则,[toUpper x | x <- xs , isAlpha x]是您通常想要的内容。

1
请看这个。
-- 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

和测试

$ convert "test1234ST" => "TEST"

0
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]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接