List 如何跟踪字符串的多个属性而不进行多次遍历?
我最近做了一个验证字符串的练习题 使用List 如何跟踪字符串的多个属性而不进行多次遍历?,list,haskell,functional-programming,traversal,List,Haskell,Functional Programming,Traversal,我最近做了一个验证字符串的练习题 使用Maybe类型编写一个计算元音数的函数 在一个字符串和辅音的数量。如果元音的数量超过 辅音的数量,函数不返回任何内容 最后,我为每个计算元音和辅音创建了两个函数: isVowel :: Char -> Bool isVowel c = c `elem` "aeiou" countVowels :: String -> Integer countVowels [] = 0 countVowels (x:xs) = if isVowel
Maybe
类型编写一个计算元音数的函数
在一个字符串和辅音的数量。如果元音的数量超过
辅音的数量,函数不返回任何内容
最后,我为每个计算元音和辅音创建了两个函数:
isVowel :: Char -> Bool
isVowel c = c `elem` "aeiou"
countVowels :: String -> Integer
countVowels [] = 0
countVowels (x:xs) =
if isVowel x
then countVowels xs + 1
else countVowels xs
countConsonants :: String -> Integer
countConsonants [] = 0
countConsonants (x:xs) =
if not $ isVowel x
then countConsonants xs + 1
else countConsonants xs
然后比较两者的值,得到我的答案
makeWord :: String -> Maybe String
makeWord [] = Nothing
makeWord s =
if countVowels s < countConsonants s
then Nothing
else Just s
我相信Haskell也有一种简单的方法,但不幸的是我并不熟悉
用什么惯用的方法来标记一个
字符串的多个属性,而不必多次遍历它?使用带有状态的foldr
有点简单:
countVCs :: String -> (Int, Int)
countVCs str = foldr k (0, 0) str
where
k x (v, c) = if isVowel x then (v + 1, c ) else (v , c + 1 )
另一种方法是Data.List.partition
,在对的两个元素上紧跟length
:
countVCs :: String -> (Int, Int)
countVCs str = both length (partition isVowel str)
where
both f (x,y) = (f x, f y)
另一种方法是将foldMap
与(Sum Int,Sum Int)
一起使用:
我首先定义传统的“指标函数”
所以
indicate . isVowel :: Char -> Integer
接下来,我将从Control.Arrow
(&&&) :: (x -> y) -> (x -> z) -> x -> (y, z)
(***) :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
所以(记住有些字符既不是元音也不是辅音)
然后我从Data.Monoid
中抓取Sum
(Sum . indicate . isVowel) &&& (Sum . indicate . isConsonant)
:: Char -> (Sum Integer, Sum Integer)
getSum *** getSum :: (Sum Integer, Sum Integer) -> (Integer, Integer)
现在我部署了foldMap
,因为我们正在进行某种单向“挤压”
然后我记得我写了一些代码,变成了控件。Newtype
,我发现下面的代码缺失了,但应该在那里
instance (Newtype n o, Newtype n' o') => Newtype (n, n') (o, o') where
pack = pack *** pack
unpack = unpack *** unpack
现在我只需要写
ala' (Sum *** Sum) foldMap ((indicate . isVowel) &&& (indicate . isConsonant))
:: String -> (Integer, Integer)
关键的小工具是
ala' :: (Newtype n o, Newtype n' o') =>
(o -> n) -> ((a -> n) -> b -> n') -> (a -> o) -> b -> o'
-- ^-packer ^-higher-order operator ^-action-on-elements
其中,打包人员的工作是选择具有正确行为实例的新类型,并确定解包人员。它完全是为了支持在本地以一种更具体的类型工作,这种类型表示预期的结构。坦率地说,函数式编程中最惯用的方法是不担心额外的遍历,而是通过更简单的解决方案来提高可读性和可组合性。两次遍历仍然是O(n)
。除非您的字符串很大或是从不可逆源(如网络流)读取,否则两次遍历的性能差异可以忽略不计
此外,您不需要使用递归COUNT元音
和COUNT辅音
可以实现为:
countVowels=长度。过滤元音
countconsones=长度。过滤器(不是.is元音)
另一种方法是使用“漂亮的折叠”库,如。然后这个问题基本上归结为编写一个表达式。我将把它分成:
import qualified Control.Foldl as L
import Control.Lens
isVowel :: Char -> Bool
isVowel c = c `elem` "aeiou"
countVowels, countConsonants :: L.Fold Char Int
countVowels = L.handles (filtered isVowel) L.length
countConsonants = L.handles (filtered (not.isVowel)) L.length
lessVowels :: L.Fold Char Bool
lessVowels = (<) <$> countVowels <*> countConsonants
makeWord :: String -> Maybe String
makeWord s = if L.fold lessVowels s then Nothing else Just s
使用foldl
可以在同一材质上同时无限多次折叠。元素序列可以是纯列表、向量或数组,也可以是管道或导管等有效流。褶皱的构成与最终折叠的内容无关
我使用了上面的手柄
,这可能有点复杂,因为它使用镜头,但这允许不同的折叠考虑不同种类的元素。所以在这里,一方面是“看”元音,另一方面是辅音。手动编写这些折叠将很简单,并且无需使用过滤的Is元音
镜头。您可以向元组中添加inc元音(v,c)=(v+1)
和incconsonal(v,c)=(v,c+1)
。显然,这些函数非常专业,但希望你能理解。我相信这是一个非常好的答案,但对于像我这样的初学者来说,这有点让我不知所措。不过,我真的很感激你的回答,等我更有经验的时候,我一定会回头看的。很抱歉,这有点太多了。当然有一大堆的概念和技术一起产生了这个解决方案,如果大部分或全部都是新的,这将需要相当多的消化。但有一个主题:我们不仅使用类型来描述有效的数据布局,还可以识别我们可以利用的计算结构(如何遍历,如何组合我们从每个元素计算的内容)。了解有用的结构(通常由类型类捕获)是学习Haskell非常有用的一部分,因为这是让编译器为您编写代码的方式。无需道歉。我一定会记住这一点,把它想象成“编译器为您编写代码”是很有趣的。我也这么想,但事实是,我甚至不知道如何实现单次遍历,即使我想这么做。我认为这是最好的实用答案。
(Sum . indicate . isVowel) &&& (Sum . indicate . isConsonant)
:: Char -> (Sum Integer, Sum Integer)
getSum *** getSum :: (Sum Integer, Sum Integer) -> (Integer, Integer)
(getSum *** getSum) .
foldMap ((Sum . indicate . isVowel) &&& (Sum . indicate . isConsonant))
:: String -> (Integer, Integer)
instance (Newtype n o, Newtype n' o') => Newtype (n, n') (o, o') where
pack = pack *** pack
unpack = unpack *** unpack
ala' (Sum *** Sum) foldMap ((indicate . isVowel) &&& (indicate . isConsonant))
:: String -> (Integer, Integer)
ala' :: (Newtype n o, Newtype n' o') =>
(o -> n) -> ((a -> n) -> b -> n') -> (a -> o) -> b -> o'
-- ^-packer ^-higher-order operator ^-action-on-elements
import qualified Control.Foldl as L
import Control.Lens
isVowel :: Char -> Bool
isVowel c = c `elem` "aeiou"
countVowels, countConsonants :: L.Fold Char Int
countVowels = L.handles (filtered isVowel) L.length
countConsonants = L.handles (filtered (not.isVowel)) L.length
lessVowels :: L.Fold Char Bool
lessVowels = (<) <$> countVowels <*> countConsonants
makeWord :: String -> Maybe String
makeWord s = if L.fold lessVowels s then Nothing else Just s
>>> makeWord "aaa"
Just "aaa"
>>> makeWord "ttt"
Nothing