String Haskell:检查字符串中的元音

String Haskell:检查字符串中的元音,string,haskell,String,Haskell,我是哈斯克尔的新手。我想写一个代码,检查字符串中是否有元音。 我提出了这个解决方案: 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

我是哈斯克尔的新手。我想写一个代码,检查字符串中是否有元音。 我提出了这个解决方案:

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)
   |
问题在哪里?

当前方法的问题 这段代码有很多问题:

  • 您定义了一个常量
    n=0
    ,并以某种方式期望这是
    检查中的默认值;事实并非如此
    
  • 你首先写
    检查n=a!!n
    ,然后使用
    检查==…
    ,一个变量只有一种类型
  • 在Haskell中,
    =
    不是赋值,而是声明。一旦声明,您就不能再更改该值
  • 你写
    check='a'|'e'|'i'|'o'|'u'|'y'
    ,但是
    |
    绑定的长度小于
    =
    ,所以你写了
    (check='a')|'e'| |'i'| | |'o'| | | | | | | | | | | y'
    ,因为没有布尔值,所以没有布尔值,不能将
    'e'
    用作
    |
    的操作数
  • 没有索引检查,因此即使上述问题得到解决
    n
    最终会增长到如此之大,以至于您将得到一个索引超出范围的异常
  • 您使用
    本身并没有错,但它被认为是低效的(O(n)),并且它不是一个如此不安全的总体函数
使用
any
让我们构造一个满足需求的函数。如果我正确理解了这些要求,您需要检查元音中是否至少有一个元素

如果字符
c
'A'
'e'
'i'
'o'
'u'
,或者
'y'
,那么它就是元音。所以我们可以开一张支票:

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

isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
vowel :: String -> Bool
vowel s = any isVowel s
vowel [] = False
vowel (x:xs) = isVowel x || vowel xs
现在我们只需要检查字符串
s
中是否有任何字符
c
是元音,这样我们就可以写:

isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
vowel :: String -> Bool
vowel s = any isVowel s
vowel [] = False
vowel (x:xs) = isVowel x || vowel xs
我们可以进一步改进这个问题。编写形式为
\x->fx
的lambda表达式是无用的。相反,我们可以简单地编写
f
。所以我们可以写:

isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
vowel :: String -> Bool
vowel s = any isVowel s
vowel [] = False
vowel (x:xs) = isVowel x || vowel xs
我们可以在
s
上应用相同的技巧:我们可以在
元音
函数的头部和身体中删除它:

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

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
函数是一个高阶函数:它将一个函数作为输入,这会让人难以理解。我们可以为
元音
函数编写自己的特殊
任意
函数。我们在这里处理的是一个列表,通常在处理列表时,我们必须考虑至少两种模式:空列表<代码>[]和非空列表<代码>(x:xs)
。由于
any
非常简单,这就足够了

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

isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
vowel :: String -> Bool
vowel s = any isVowel s
vowel [] = False
vowel (x:xs) = isVowel x || vowel xs
如果列表非空
(x:xs)
,则它有一个头部(第一个元素)
x
和一个尾部(剩余元素)
xs
。如果第一个元素是元音,或者任何其他元素是元音,则字符串包含元音。所以我们可以写:

isVowel :: Char -> Bool
isVowel c = elem c "aeiouy"
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
vowel :: String -> Bool
vowel s = any isVowel s
vowel [] = False
vowel (x:xs) = isVowel x || vowel xs
如果我们把这些放在一起(连同
isVowel
函数),我们可以得到:

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

vowel [] = False
vowel (x:xs) = isVowel x || vowel xs

您的代码需要更多的关注。然而,这个具体的错误是相当直接的。运算符
|
需要两个
Bool
值。但是,诸如
'a'
'b'
等值属于
Char
类型,因此不能在此处使用
|

您需要的是一系列布尔值来使用
|
。第一个值,
check=='a'
,理论上是一个
Bool
,因为操作符
==
可以取两个值(某些类型,包括
Char
)并返回
Bool
。那么,您想要的是创建一系列比较,以得到许多布尔值。因此,您必须使用
=
操作符来比较
检查
每个元音:

| check == 'a' || check ==  'e' || check ==  'i' || check ==  'o' || check ==  'u' || check ==  'y'   = True
这是开始编写代码时常见的误解。用英语可以说

如果
检查
等于
'a'
,或
'e'
,或
'i'

我们试着把它直接翻译成代码。然而,在Haskell中,“or”操作符(
|
)无法理解这意味着什么。您必须明确每个条件:

如果
检查
等于
'a'
,或
检查
等于
'e'
,或
检查
等于
'i'

一旦你解决了这个问题,你的代码会工作吗?不,还有其他问题。例如,
check
没有在任何地方定义。尽管如此,每次解决一个问题,然后转到下一个问题,最终你会得到你想要的:)

问题0 您已经定义了
n=0
。我想你是想把它当作一种在每个递归步骤中都会增加的运行变量。这在Haskell中是不可能的–如果在顶层定义
n=0
,那么
n
将始终是
0

要拥有这样一个“运行变量”,需要将其作为递归循环函数的参数。您似乎尝试过使用
check
及其
n
参数执行类似操作(这是一个完全不同的变量,它会对全局
n
进行阴影处理–阴影处理很容易导致混淆,请避免)。这种本地函数的标准名称是
go
,一个