将大写字母转换为小写字母并在Haskell中计算字符串

时间:2017-12-18 20:38:48

标签: haskell

我是Haskell的新手,我正在尝试将输入的字符串转换为小写字母。转换后,我想对来自['a'..'z']的所有小写字母进行计数。

例如:“这是TES3T” 结果:[(t,3),(h,1),(i,2),(s,2),(a,1)(e,1)]

这是我到目前为止所做的:

countL :: [Char] -> Char -> Int
countL s c = length ( [x | x <- s, x == c])

letter_count :: [Char] -> [(Char, Int)]
letter_count s = nub [(c, countL s c) | c <- s]

我发现以下内容将给定的字符串转换为小写:

toLowerString :: [Char] -> [Char]
toLowerString str = [ toLower x | x <- str]

但是我不知道如何在我的“letter_count”函数中使用“toLowerString”并且只读取['a'..'z']。

我完全错了吗?是否有一种简单的方法可以做到这一点?

谢谢!

3 个答案:

答案 0 :(得分:3)

计算字母的一个很好的简短解决方案是使用模块grouplink)中的函数Data.List。它需要一个列表并返回其分组元素的列表。要获得唯一组,只需先对输入字符串进行排序。 E.g:

group (sort "Hello World")

会给你:

[" ","H","W","d","e","lll","oo","r"]

您需要做的就是将每个子字符串转换为其长度和首字母的元组:

map (\s -> (head s, length s))

所以你的letter_count将是:

letter_count :: [Char] -> [(Char, Int)]
letter_count = map (\s -> (head s, length s)) . group . sort

答案 1 :(得分:1)

您的解决方案中有一些优化空间。

首先,函数letter_count在O(n ^ 2)时间运行:当计算[(c, countL s c) | c <- s]的结果时,对于n个字母的每个字符字符串O {n)查找由countL执行。这不是最佳时间复杂度 - 计算字母可以更快地完成。

如果您只想考虑字母[a,...,z],那么可以使用Data.Array中的数组在O(n)时间内完成计数(或者,如评论中提到的@dfeuer,在O(n log k)时间内使用Data.IntMap.Strict。如果你想坚持列表 - 这将导致更简单的代码(特别是初学者需要),你可以实现O(n log(n))通过先排序列表然后计算字母来确定复杂性。

排序后,您确定相同的字母会出现在连续的块中,因此您可以轻松地对它们进行分组并以线性时间计算它们。

第二次优化远没那么重要,但它可以提高代码的清晰度。看看你对toLowerString

的定义
toLowerString :: [Char] -> [Char]
toLowerString str = [ toLower x | x <- str]

你在片段[ toLower x | x <- str]中所做的正是map函数的作用 - 你用一些函数的结果替换某些列表的每个元素(这里:toLower )。您可以像这样重写此部分:

toLowerString :: [Char] -> [Char]
toLowerString str = map toLower str

此外,现在您可以观察到,可以省略定义中的str参数 - 代码变得更短:

toLowerString :: [Char] -> [Char]
toLowerString = map toLower

我对您的问题的解决方案如下:

import Data.Char (isLetter, toLower)
import Data.List (sort, group)

countLetters str = 
  let 
    filteredStr = filter isLetter str
    lowerCaseStr = map toLower filteredStr
    sortedStr = sort lowerCaseStr
    groupedStr = group sortedStr
  in
  [(h, length fragment) | fragment@(h:_) <- groupedStr]

main = do
  putStrLn "Type the string to count letters in:"
  string <- getLine
  let result = countLetters string
  print result

你可以在这里试试: https://repl.it/repls/GrubbyUnnaturalKitty

如果您想让这个解决方案更短,您可以这样写:

countLetters str = 
  let 
    groupedStr = group . sort . map toLower . filter isLetter $ str
  in
  [(head fragment, length fragment) | fragment <- groupedStr]

使用函数组合运算符.。棘手的一点可能是$运算符的使用,它实际上什么也没做 - 它将左边的函数应用于右边给出的参数。它唯一的作用是帮助避免写太多括号 - 没有它,这条线看起来像这样:

    groupedStr = (group . sort . map toLower . filter isLetter) str

请注意两种不同的方法来提取字母组的头部,同时还将名称fragment绑定到整个组:

A。将整个群组命名为fragment,并在结果元组的定义中使用函数head

  [(head fragment, length fragment) | fragment <- groupedStr]

B。将整个群组命名为片段,但也使用h符号将其标题为@

  [(h, length fragment) | fragment@(h:_) <- groupedStr]

基本上@允许你执行模式匹配,同时在匹配发生之前引入整个事物的名称。

答案 2 :(得分:1)

Radek提到的阵列解决方案如下所示:

import qualified Data.Array.Unboxed as A
import Data.Char ( isAsciiLower )

countLettersArr :: [Char] -> [(Char, Int)]
countLettersArr cs = filter ((/= 0) . snd) (A.assocs arr)
  where
    arr :: A.UArray Char Int
    arr = A.accumArray (+) 0 ('a', 'z')
          [(c, 1 :: Int) | c <- cs, isAsciiLower c]

countLettersArr将删除任何非小写字母的字符。这可以稍微概括一下。启用{-# LANGUAGE ScopedTypeVariables #-}

countIxesArr :: forall c. A.Ix c => (c,c) -> [c] -> [(c, Int)]
countIxesArr (lo,hi) cs = filter ((/= 0) . snd) (A.assocs arr)
  where
    arr :: A.UArray c Int
    arr = A.accumArray (+) 0 (lo, hi)
          [(c, 1::Int) | c <- cs, A.inRange (lo,hi) c]

效率稍低的方法是使用IntMap而不是数组。这有点痛苦,因为IntMap只接受Int索引,但它的工作方式几乎相同:fromListWithaccumArray非常相似。此版本将记录您抛出的任何字符,小写字母与否。你可以通过在im的定义中为列表理解添加一个保护来轻松地改变它。

import qualified Data.IntMap.Strict as M
import Data.Char (ord, chr)
import Data.Bifunctor (first)

-- Count how many of each letter appear in a string.
countLettersIM :: [Char] -> [(Char, Int)]
countLettersIM cs = map (first chr) . M.toList $ im
  where
    im :: M.IntMap Int
    im = M.fromListWith (+) [(ord c, 1) | c <- cs]

如果您愿意,也可以将其概括为一个方向:

countEnumsIM :: Enum a => [a] -> [(a, Int)]
countEnumsIM cs = map (first toEnum) . M.toList $ im
  where
    im = M.fromListWith (+) [(fromEnum c,1) | c <- cs]