[英]Haskell: checking the vowels in a String
我是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)
|
問題出在哪兒?
這段代碼有很多問題:
n = 0
,並以某種方式期望這是一個check
的默認值; 事實並非如此; check n = a !! n
check n = a !! n
,然后使用check == ...
,變量只有一種類型; =
不是 賦值 ,它是一個聲明 。 聲明后,您無法再更改該值; check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'
check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'
check == 'a' || 'e' || 'i' || 'o' || 'u' || 'y'
,但是||
綁定比==
少,所以你寫(check == 'a') || 'e' || 'i' || 'o' || 'u' || 'y'
(check == 'a') || 'e' || 'i' || 'o' || 'u' || 'y'
(check == 'a') || 'e' || 'i' || 'o' || 'u' || 'y'
,因為字符'e'
不是布爾值(並且在Haskell中沒有真實性 ),你不能使用'e'
作為||
的操作數 ; n
最終會變得那么大,你會得到一個超出范圍異常的索引 ; !!
這本身並不錯,但它被認為是效率低下的( O(n) )並且它不是一個如此不安全的總函數。 any
讓我們構建一個滿足需求的函數。 如果我正確理解了要求,您需要檢查元音中是否至少有一個元素。
如果是'a'
, 'e'
, 'i'
, 'o'
, 'u'
或'y'
,則字符c
是元音。 所以我們可以寫一張支票:
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"
現在我們只需要檢查字符串s
中是否有任何字符c
是元音,所以我們可以寫:
vowel :: String -> Bool
vowel s = any (\c -> isVowel c) s
我們可以進一步改善這個問題。 編寫形式為\\x -> fx
的lambda表達式是沒用的。 相反,我們可以簡單地寫f
。 所以我們可以寫:
vowel :: String -> Bool
vowel s = any isVowel s
我們可以在s
上應用相同的技巧:我們可以在vowel
函數的頭部和主體中刪除它:
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
函數是一個高階函數:它將一個函數作為輸入,這使得它很難理解。 我們可以為vowel
函數編寫我們自己的specialed any
函數。 我們在這里使用列表,通常在進行列表處理時,我們必須考慮至少兩種模式:空列表[]
和非空列表(x:xs)
。 由於any
相當簡單,這就足夠了。
如果我們處理空列表,我們知道字符串中沒有元音,所以我們可以寫:
vowel [] = False
在列表非空(x:xs)
,它具有頭部 (第一元素) x
和尾部 (剩余元素) xs
。 如果第一個元素是元音,或者任何其余元素是元音,則字符串包含元音。 所以我們可以寫:
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
,因為operator ==
可以取兩個值(某些類型,包括Char
)並返回Bool
。 那么你想要的是創造一系列的比較以獲得許多布爾。 因此,您必須使用==
運算符來比較每個元音的check
:
| check == 'a' || check == 'e' || check == 'i' || check == 'o' || check == 'u' || check == 'y' = True
這在開始編碼時是一種常見的誤解。 有人可以說,用英語
如果
check
等於'a'
,或'e'
,或'i'
......
我們嘗試將其直接翻譯成代碼。 然而,在Haskell中,“或”運算符( ||
)無法弄清楚這意味着什么。 你已經明確了每一個條件:
如果
check
等於'a'
,或check
等於'e'
,或check
等於'i'
...
一旦解決了這個問題,你的代碼會起作用嗎? 不,還有其他問題。 例如, check
未在任何地方定義。 盡管如此,每次解決一個問題並轉移到下一個問題,最終你會得到你想要的:)
你定義了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) ...
您使用列表索引來遍歷列表的所有元素。 這是笨拙和不安全的(當你在列表之外索引時給出一個模糊的錯誤) † ; 這樣做的首選方法是使用標准折疊操作 (見下文),或者通過模式匹配直接在列表上編寫遞歸,而不是整數索引變量。 所以我們可以再次刪除那個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
預定義,所以你不妨把遞歸拉到頂層:
vowel :: String -> Bool
vowel [] = ...
vowel (x:xs) = ... vowel xs ...
你一直在努力,宣布check
在保護名單。 這是不支持的-見上面, where
應該用於當地的定義,或者let
‡。
您已嘗試在||
的單個鏈中編寫多個相等選項 。 這是不可能的,因為每次比較都采用(在這種情況下)數字並給出Bool
結果,因此您不能只鏈接它們。 你能做的就是寫出x == 'a' || x == 'e' || ...
x == 'a' || x == 'e' || ...
x == 'a' || x == 'e' || ...
,但那顯然很尷尬。 正如Willem所建議的那樣 ,一個更簡潔的選擇是編寫x `elem` "aeiouy"
代替:這將是正確的事情。
如果您有沒有碰到過這些反引號來``
尚未:他們采取像兩個參數的函數elem
,放入綴模式 。 這類似於n + 1
的中綴+
。
關於綴的好處是,你可以使部分人:定義需要一個字符,它與每一個元音比較功能,你可以寫
isVowel c = elem c "aeiouy"
但你也可以寫
isVowel = (`elem` "aeiouy")
請注意,我通過eta減少完全消除了變量。 這稱為無點樣式 。
這很方便:沒有必要將isVowel
定義為應用程序的命名函數 - 無論如何只能將它用於一個目的,即與字符串中的所有字符進行比較。 適用於容器中的所有元素的運行和合並結果的最常見方式¶是foldr
。 你可以像使用它一樣
vowel = foldr ((||) . (`elem` "aeiouy")) False
這有點神秘; 幸運的是,logical-or和fold的組合有一個標准名稱: any
。 有了它,你可以盡可能簡潔
vowel = any (`elem` "aeiouy")
† 索引到列表也是非常低效的: !!
需要遍歷所有元素到所請求的元素。 當O ( n )完全足夠時,這會使您的嘗試復雜度為O ( n 2 )。
‡ 順便說一句,這是可能的,但只能通過濫用定義在單個保護什么模式防護功能 。
¶ 要僅在容器中再次收集結果,您可以使用更簡單的fmap
。
對於可能發現此問題的中間Haskeller,使用高階函數的簡單解決方案:
checkVowels :: String -> String
checkVowels = any (`elem` "aeiouyw")
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.