Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
List 如何跟踪字符串的多个属性而不进行多次遍历?_List_Haskell_Functional Programming_Traversal - Fatal编程技术网

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