Performance Haskell函数的简单实现比';更聪明';解决方案
所以我是Haskell的新手,我试图弄明白为什么我的天真实现实际上比我认为的更智能的解决方案更快 我试图编写一个函数,给定一个Performance Haskell函数的简单实现比';更聪明';解决方案,performance,haskell,lazy-evaluation,Performance,Haskell,Lazy Evaluation,所以我是Haskell的新手,我试图弄明白为什么我的天真实现实际上比我认为的更智能的解决方案更快 我试图编写一个函数,给定一个String将返回一个Bool,指示字符串是否使用一个元音。以下是我的天真实现: singleVowel :: Maybe String -> Bool singleVowel Nothing = False singleVowel (Just s) = singleVowel (Just s) = length (nub $ filter (`elem` "aei
String
将返回一个Bool
,指示字符串是否使用一个元音。以下是我的天真实现:
singleVowel :: Maybe String -> Bool
singleVowel Nothing = False
singleVowel (Just s) = singleVowel (Just s) = length (nub $ filter (`elem` "aeiouyAEIOUY") s) == 1
请注意,我过滤掉了不在元音集中的所有元素。然后,我使用nub
函数再次传递,以从过滤列表中删除重复的元音,并查看列表中是否正好有1个元音。然后在最坏的情况下,该解决方案将使用O(n)
内存和时间,因为它必须为过滤列表分配内存
现在,对于我的另一个解决方案,我决定使用递归,并在每次递归调用中传递一个字符,以跟踪当前元音(如果有)
singleVowelFaster :: Maybe String -> Bool
singleVowelFaster Nothing = False
singleVowelFaster (Just s) = singleVowelHelper s Nothing
singleVowelHelper :: String -> Maybe Char -> Bool
singleVowelHelper [] Nothing = False
singleVowelHelper [] _ = True
singleVowelHelper (x:xs) Nothing = if x `elem` "aeiouyAEIOUY"
then singleVowelHelper xs (Just x)
else singleVowelHelper xs Nothing
singleVowelHelper (x:xs) (Just c) =
(x `elem` "aeiouyAEIOUY" && c == x) && singleVowelHelper xs (Just c)
但出于某些奇怪的原因,“天真”的实现比“更智能”的解决方案运行得更快
可能是因为Haskell是懒惰的,所以没有对(x
elem“aeiouyaeouy”和&c==x)进行评估,所以一旦达到基本情况,就会对所有thunk进行评估,从而导致比“幼稚”实现更慢
如果是这样的话,那么当我将(x
elem“aeiouyaeouy”&&c==x)放入If语句以强制评估函数的性能时,为什么会是这样呢
我说的第二个函数有O(1)
空间使用和O(n)
时间吗?最简单的解决方法是颠倒第一个&
调用的顺序,在昂贵的元素检查之前执行廉价的=
检查
这将使您的函数运行更快…但它仍然是不正确的!你基本上是在断言,在第一个元音之后,所有下面的字符都是完全相同的元音;您打算做的是断言以下所有元音都是相同的元音。我会这样写:
(x == c || not (x `elem`vowels)) && singleVowelHelper xs (Just c)
或者,好吧,我真的会以不同的方式编写整个函数,但以上是使实现工作的最简单的更改。下面是我将要编写的实现,从一个更通用的基于谓词的函数开始,然后专门处理元音:
exactlyOne :: Eq a => (a -> Bool) -> [a] -> Bool
exactlyOne pred [] = False
exactlyOne pred (x:xs)
| pred x = not . any pred . filter (/= x) $ xs
| otherwise = exactlyOne pred xs
exactlyOneVowel :: String -> Bool
exactlyOneVowel = exactlyOne (`elem` "aeiouAEIOU")
最简单的解决方法是颠倒第一次&&
调用的顺序,在昂贵的元素检查之前执行廉价的=
检查
这将使您的函数运行更快…但它仍然是不正确的!你基本上是在断言,在第一个元音之后,所有下面的字符都是完全相同的元音;您打算做的是断言以下所有元音都是相同的元音。我会这样写:
(x == c || not (x `elem`vowels)) && singleVowelHelper xs (Just c)
或者,好吧,我真的会以不同的方式编写整个函数,但以上是使实现工作的最简单的更改。下面是我将要编写的实现,从一个更通用的基于谓词的函数开始,然后专门处理元音:
exactlyOne :: Eq a => (a -> Bool) -> [a] -> Bool
exactlyOne pred [] = False
exactlyOne pred (x:xs)
| pred x = not . any pred . filter (/= x) $ xs
| otherwise = exactlyOne pred xs
exactlyOneVowel :: String -> Bool
exactlyOneVowel = exactlyOne (`elem` "aeiouAEIOU")
奇怪的是,即使有这么快的变化,它仍然比第一个函数运行得慢。我的测试用例是最糟糕的:SingleVowerFaster$Just$take 100000$repeat'a'Huh?您从未显示过任何singlevouelfaster
的源代码,也从未显示过任何与该类型签名匹配的函数。如果您以这种方式调用函数,那么它必须具有类型singlevouelfaster::Maybe[Char]->Bool
,这毫无意义。请发布您正在运行的实际代码。哦,我的错。更新了上面的问题。奇怪的是,即使有这么快的变化,它仍然比第一个函数运行得慢。我的测试用例是最糟糕的:SingleVowerFaster$Just$take 100000$repeat'a'Huh?您从未显示过任何singlevouelfaster
的源代码,也从未显示过任何与该类型签名匹配的函数。如果您以这种方式调用函数,那么它必须具有类型singlevouelfaster::Maybe[Char]->Bool
,这毫无意义。请发布您正在运行的实际代码。哦,我的错。更新了上面的问题。签名可能是字符串->Bool
在这里很奇怪。你为什么不干脆用String->Bool
并忘掉Nothing这个例子呢?啊,我只是在试验Maybe类型类,这真的没有必要。签名Maybe String->Bool
在这里很奇怪。你为什么不直接使用String->Bool
并忘记Nothing这个例子呢?啊,我只是在尝试使用Maybe类型类,这真的没有必要。