簡體   English   中英

Haskell:檢查字符串中的元音

[英]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 == ... ,變量只有一種類型;
  • 在Haskell中= 不是 賦值 ,它是一個聲明 聲明后,您無法再更改該值;
  • 你寫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'

但這是相當不優雅的。 因為StringChar的列表,我們可以使用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未在任何地方定義。 盡管如此,每次解決一個問題並轉移到下一個問題,最終你會得到你想要的:)

問題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

您使用列表索引來遍歷列表的所有元素。 這是笨拙和不安全的(當你在列表之外索引時給出一個模糊的錯誤) ; 這樣做的首選方法是使用標准折疊操作 (見下文),或者通過模式匹配直接在列表上編寫遞歸,而不是整數索引變量。 所以我們可以再次刪除那個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 ...

問題2

你一直在努力,宣布check在保護名單。 這是不支持的-見上面, where應該用於當地的定義,或者let ‡。

問題3

您已嘗試在||的單個鏈中編寫多個相等選項 這是不可能的,因為每次比較都采用(在這種情況下)數字並給出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")

索引到列表也是非常低效的: !! 需要遍歷所有元素到所請求的元素。 On )完全足夠時,這會使您的嘗試復雜度為On 2 )。

順便說一句,這可能的,但只能通過濫用定義在單個保護什么模式防護功能

要僅在容器中再次收集結果,您可以使用更簡單的fmap

對於可能發現此問題的中間Haskeller,使用高階函數的簡單解決方案:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM