【问题标题】:Capitalizing first letter of words while removing spaces (Haskell)删除空格时将单词的首字母大写(Haskell)
【发布时间】:2019-09-24 20:33:51
【问题描述】:

我刚开始使用 Haskell,这就像我要写的第三件事,所以,自然地,我发现自己有点难过。

我正在尝试编写一些代码,该代码将接受一个字符串、删除空格并将该字符串的每个字母大写。

例如,如果我输入“这是一个测试”,我想返回类似:“thisIsATest”的内容

import qualified Data.Char as Char

toCaps :: String -> String
toCaps [] = []
toCaps xs = filter(/=' ') xs
toCaps (_:xs) =  map Char.toUpper xs 

我认为我使用的方法是错误的。按此顺序使用我的代码,我可以使用 filter 函数删除所有空格,但没有任何内容变为大写。

当我将filter 位移动到代码的最末尾时,我可以使用map Char.toUpper 位。例如,当我映射该函数Char.toUpper 时,它只是将所有“HISISATEST”都大写。 我试图使用 if 函数来表达类似于

的内容

if ' ' then map Char.toUpper xs else Char.toLower xs,但这对我没有用。我还没有在 Haskell 中使用 if,而且我认为我做得不对。我也知道使用“xs”是错误的,但我不知道如何解决它。 任何人都可以就这个特定问题提供任何指示吗?

【问题讨论】:

  • 最后一个子句永远不会触发,因为第二个模式toCaps xs 将捕获所有可能的输入。
  • 使用-Wall打开警告会检测到这个错误。
  • 如果有前导空格怎么办? toCaps " hey"应该是"hey"(因为'h'是第一个非空格字符)还是"Hey"(因为'h'不是第一个字符)?

标签: haskell


【解决方案1】:

我认为将问题拆分为更小的子问题可能会更好。首先,我们可以创建一个函数,对于给定的单词,它将首字母大写。对于 camel case,我们可以这样实现:

import Data.Char(toUpper)

capWord :: String -> String
capWord "" = ""
capWord (c:cs) = toUpper c : cs

然后我们可以使用words来获取单词列表:

toCaps :: String -> String
toCaps = go . words
    where go [] = ""
          go (w:ws) = concat (w : map capWord ws)

例如:

Prelude Data.Char> toCaps "this is a test"
"thisIsATest"

对于Pascal 情况,我们可以使用concatMap 代替:

toCaps :: String -> String
toCaps = concatMap capWord . words

【讨论】:

  • go 不只是重新实现(>>=) 吗? toCaps = (>>= capWord) . words?
  • @amalloy:如果我正确阅读了秒,则第一个单词的第一个字符不应大写(示例中建议这样做)。所以我认为它基本上是从给定的字符串构造“camelCase”。
  • 你可以使用w++concatMap ...
【解决方案2】:

this answer from Will Ness 的启发,这是一种避免不必要的布尔值和比较的方法:

import qualified Data.Char as Char

toCaps :: String -> String
toCaps = flip (foldr go (const [])) id
  where go ' ' acc _ =     acc Char.toUpper
        go x   acc f = f x:acc id

或者更容易理解,但效率可能略低:

import qualified Data.Char as Char

toCaps :: String -> String
toCaps = go id
  where go _ []       =     []
        go _ (' ':xs) =     go Char.toUpper xs
        go f (x  :xs) = f x:go id           xs

【讨论】:

  • 使用函数而不是布尔值...我希望如果我们使用 Church 编码的布尔值并简化,这就是我们最终得到的代码! -- 说到效率,我们甚至可以多做一步,把reducer和selector函数挤在一起,去掉那些烦人的ids,最后让两个reducer函数切换起来它们之间。 :) (当然,只有direct recursion 才有可能;像这样的 irregular 模式没有自己的名字)。我已经在下面发布了它作为答案。 (那里是一个非常类似于 Prolog 的代码)。 :)
【解决方案3】:

有很多方法可以做到这一点,但如果我试图让它尽可能接近您设置示例的方式,我可能会执行以下操作:

import Data.Char (toUpper)

toCaps :: String -> String
toCaps []         = []                    -- base case
toCaps (' ':c:cs) = toUpper c : toCaps cs -- throws out the space and capitalizes next letter
toCaps (c:cs)     = c : toCaps cs         -- anything else is left as is

这只是使用基本递归,一次处理一个字符(列表的元素),但如果您想使用适用于整个列表的高阶函数,例如 mapfilter,那么您可能想要组合它们(Willem 建议的方式是一种方式),在这种情况下,您可能根本不使用递归。

应该注意的是,这个解决方案是脆弱的,因为它假设输入字符串不包含前导、尾随或多个连续空格。

【讨论】:

  • 如果连续有两个空格,这将无法正常工作。
  • 如果空格是最后一个字符,它也不能正常工作。
  • 谢谢!更新了关于此的注释。
【解决方案4】:

Joseph Sibleanswer 启发,协程 解决方案:

import Data.Char

toCamelCase  :: String -> String
toCamelCase  []         =  []
toCamelCase  (' ': xs)  =  toPascalCase xs
toCamelCase  (x  : xs)  =          x : toCamelCase xs

toPascalCase :: String -> String
toPascalCase []         =  []
toPascalCase (' ': xs)  =  toPascalCase xs
toPascalCase (x  : xs)  =  toUpper x : toCamelCase xs

注意不要以空格开头输入字符串,否则第一个单词也会大写。

【讨论】:

  • 如果字符串有一个尾随空格,这不会删除它。不过,这看起来不错!
  • 呵呵,我测试了尾随空格,但没有测试 single 空格!谢谢你发现这个。
  • 我将您的函数从where 中提取出来,并给了它们更有意义的名称。如果您不同意,请随时回复。
  • 谢谢;有趣的是,意义是如何从最小解决方案中得出的。前导空格的跳过仍然需要单独完成,但这是dropWhile isSpace 已经捕获的众所周知的模式。
  • (对于普通读者:如果不清楚的话,Joseph 发现的错误在当时已经修复)。
猜你喜欢
  • 1970-01-01
  • 2023-01-26
  • 2015-11-10
  • 1970-01-01
  • 2015-11-04
  • 2012-11-11
  • 2010-12-05
  • 2012-07-24
  • 2016-02-15
相关资源
最近更新 更多