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

我只是想让它工作,我什至还没有开始担心将小写字母更改为大写字母。

任何正确方向的观点都将非常感激。

编辑:应该提到从低到高的转换不能使用:如果,那么,否则。简单地列出理解和列出运算符。

4

4 回答 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 ]

尽管您也可以使用多变量列表表达式,但递归表达要复杂一些。因此,如果您了解递归,那么列表推导式对您来说应该是微不足道的。

于 2013-02-16T23:19:02.190 回答
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"
于 2013-02-17T00:05:22.517 回答
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]]

但是,您可能会觉得使用这些列表功能是作弊。真正的程序员避免任何外部依赖。遵循这一理念,我们可以在列表理解中引导大部分前奏:

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 无法在列表解析的主体中声明新的数据类型。这意味着我们不能清除我们对前奏的 Char、String 和 Bool 类型的依赖。

否则,[toUpper x | x <- xs , isAlpha x]这就是您通常想要的。

于 2013-02-16T23:22:48.097 回答
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]
于 2013-02-16T23:16:56.813 回答