Haskell:检查字符串中的元音

时间:2017-09-22 12:25:08

标签: string haskell

我是Haskell的新手。我想编写一个代码,检查String中是否有元音。 我提出了这个解决方案:

n :: Int
n = 0

vowel :: String -> Bool
vowel a
    | check n = a !! n   
    | check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'   = True  
    | check /= 'a' || 'e' || 'i' || 'o' || 'u' || 'y'   = check(n+1) 
    | otherwise = False 

所以基本上,如果有元音,我想用递归来逐字检查。 我刚收到错误消息:

 Couldn't match expected type `Bool' with actual type `Char'
    * In the second argument of `(||)', namely 'y'
      In the second argument of `(||)', namely 'u' || 'y'
      In the second argument of `(||)', namely 'o' || 'u' || 'y'
   |
14 |                 | check /= 'a' || 'e' || 'i' || 'o' || 'u' || 'y'   = check(n+1)
   |

问题出在哪里?

4 个答案:

答案 0 :(得分:11)

当前方法的问题

这段代码有很多问题:

  • 您定义了一个常量n = 0,并以某种方式预期这是check中的默认值;事实并非如此;
  • 您先写check n = a !! n,然后使用check == ...,变量只有一种类型;
  • 在Haskell中= 作业,它是声明。声明后,您无法再更改该值;
  • 你写的是check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y',但是||==更少,所以你写了(check == 'a') || 'e' || 'i' || 'o' || 'u' || 'y',因为一个字符'e'不是布尔值(和在Haskell中没有真实性),你不能使用'e'作为||的操作数;
  • 没有索引检查,即使上述问题得到解决; n最终会变得那么大,你会得到索引超出范围的异常;
  • 你使用的!!本身并没有错,但它被认为效率低下( O(n))并且它不是一个完整的函数,所以不安全

使用any

让我们构建一个满足需求的函数。如果我正确理解了要求,你需要检查元音中是否至少有一个元素。

字符c如果是'a''e''i''o''u'或{{1},则为元音}}。所以我们可以写一张支票:

'y'

但这是相当不优雅的。由于isVowel :: Char -> Bool isVowel c = c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' || c == 'y' String的列表,我们可以使用Char函数,因此我们可以写:

elem :: Eq a => a -> [a]

现在我们只需要检查字符串isVowel :: Char -> Bool isVowel c = elem c "aeiouy" 中是否有任何字符c是元音,所以我们可以写:

s

我们可以进一步改善这个问题。编写vowel :: String -> Bool vowel s = any (\c -> isVowel c) s 形式的lambda表达式是没用的。相反,我们可以简单地写\x -> f x。所以我们可以写:

f

我们可以在vowel :: String -> Bool vowel s = any isVowel s 上应用相同的技巧:我们可以在s函数的头部和正文中删除它:

vowel

最后,我们可以使用vowel :: String -> Bool vowel = any isVowel 来设置isVowel函数无点:

flip

这导致:

isVowel :: Char -> Bool
isVowel = flip elem "aeiouy"

然后我们可以测试它。例如:

isVowel :: Char -> Bool
isVowel = flip elem "aeiouy"

vowel :: String -> Bool
vowel = any isVowel

使用递归

Prelude> vowel "foobar" True Prelude> vowel "qx" False Prelude> vowel "bl" False Prelude> vowel "bla" True 函数是一个更高阶函数:它将一个函数作为输入,这使得它很难理解。我们可以为any :: (a -> Bool) -> [a] -> Bool函数编写我们自己的specialed any函数。我们在这里使用列表,通常在进行列表处理时,我们必须考虑至少两种模式:空列表vowel和非空列表[]。由于(x:xs)相当简单,这就足够了。

如果我们处理空列表,我们知道字符串中没有元音,所以我们可以写:

any

如果列表非空vowel [] = False ,则它有 head (第一个元素)(x:xs) tail (其余元素) )x。如果第一个元素是元音,或者任何其余元素是元音,则字符串包含元音。所以我们可以写:

xs

如果我们将它们放在一起(与vowel (x:xs) = isVowel x || vowel xs 函数一起),我们得到:

isVowel

答案 1 :(得分:3)

您的代码需要更多关注。然而,这个特定错误非常简单。运算符||需要两个Bool值。 'a''b'等值的类型为Char,但是,您不能在此处使用||

您需要的是使用||的一系列布尔值。理论上,第一个值check == 'a'Bool,因为运算符==可以采用两个值(某些类型,包括Char)并返回Bool }。那么你想要的是创造一系列的比较以获得许多布尔。因此,您必须使用==运算符将check与每个元音进行比较:

| check == 'a' || check ==  'e' || check ==  'i' || check ==  'o' || check ==  'u' || check ==  'y'   = True

这是开始编码时常见的误解。用英语,可以说

  

如果check等于'a',或'e',或'i' ......

我们尝试将其直接翻译成代码。然而,在Haskell中,"或" operator(||)无法弄清楚这意味着什么。你已经明确了每一个条件:

  

如果check等于'a',或check等于'e',或check等于'i' ......

解决此问题后,您的代码是否有效?不,还有其他问题。例如,check未在任何地方定义。尽管如此,每次解决一个问题并转移到下一个问题,最终你会得到你想要的东西:)

答案 2 :(得分:1)

问题0

您已定义n = 0。我怀疑你打算用它作为一种在每个递归步骤中都增加的运行变量。这在Haskell中是不可能的 - 如果您在顶层定义n = 0,那么n 总是0

要拥有这样一个“运行变量”,你需要使它成为递归循环函数的参数。您好像尝试使用check及其n参数进行类似的操作(这是一个完全不同的变量,阴影全局n - 阴影可以容易导致混淆,避免它)。这种本地函数的标准名称是go,它将像

一样使用
vowel :: String -> Bool
vowel a = go 0
 where go n = case a !! n of
          ...  ->  False
          ...  ->  True
          ...  -> ... go (n+1) ...

问题1

使用list-indexing循环遍历列表的所有元素。这是笨拙和不安全的(当你在列表之外索引时给出一个模糊的错误);做这样的事情的首选方法是使用the standard folding operations(见下文),或者通过模式匹配直接在列表上编写递归,而不是整数索引变量。因此,我们可以再次删除n参数,并在列表中递归:

vowel :: String -> Bool
vowel a = go a
 where go (x:xs) = case x of
          ...  ->  False
          ...  ->  True
          ...  -> ... go xs ...

请注意,这会在列表末尾自动失败,因为模式x:xs不再匹配。简单明了,只需添加一个特殊条款:

 where go (x:xs) = case x of
          ...  ->  False
          ...  ->  True
          ...  -> ... go xs ...
       go [] = ...

实际上,此时,go只是vocal的本地预定义,因此您不妨将递归拉到顶层:< / p>

vowel :: String -> Bool
vowel [] = ...
vowel (x:xs) = ... vowel xs ...

问题2

您已尝试在保护列表中声明check。这不受支持 - 见上文,where应该用于本地定义,或者let

问题3

您已尝试在||的单个链中编写多个相等选项。这是不可能的,因为每次比较都采用(在这种情况下)数字并给出Bool结果,因此您不能只链接它们。你可以做的是写出x == 'a' || x == 'e' || ...,但这显然很尴尬。一个更简洁的选项,suggested by Willem,就是写x `elem` "aeiouy"代替:这将是正确的事情。

如果您还没有遇到过这些反引号``:它们会使用elem之类的双参数函数并将其置于中缀模式。这类似于+中的中缀n + 1

关于中缀的好处是你可以创建一个section:定义一个带字符的函数并将它与每个元音进行比较,你可以编写

isVowel c = elem c "aeiouy"

但你也可以写

isVowel = (`elem` "aeiouy")

请注意,我已通过eta reduction完全删除了变量。这称为point-free style

这就派上用场了:没有必要将isVowel定义为应用程序的命名函数 - 无论如何只能将它用于一个目的,即与字符串中的所有字符进行比较。将操作应用于容器的所有元素并将结果组合的最常用方法是foldr。您可以像

一样使用它
vowel = foldr ((||) . (`elem` "aeiouy")) False

这有点神秘;幸运的是,logical-or和fold的组合有一个标准名称:any。有了它,你可以像

一样简洁
vowel = any (`elem` "aeiouy")

索引到列表中的效率也非常低:!!需要遍历所有元素,直到所请求的元素。当 O n 时,这会使您尝试复杂的 O n 2 ) em>)就足够了。

顺便提一下, 可以在单个警卫中定义某些东西,但只能滥用pattern guard feature < / p>

要在容器中再次收集结果,您可以使用更简单的fmap

答案 3 :(得分:0)

使用高阶函数的简单解决方案,适用于可能会发现此问题的中间Haskeller:

checkVowels :: String -> String
checkVowels = any (`elem` "aeiouyw")