【问题标题】:Naive implementation of Haskell function is faster than 'smarter' solution?Haskell 函数的幼稚实现比“更智能”的解决方案更快?
【发布时间】:2016-03-24 11:46:51
【问题描述】:

所以我是 Haskell 的新手,我试图弄清楚为什么我的幼稚实现实际上比我认为的更智能的解决方案要快。

我正在尝试编写一个函数,给定一个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)

但由于某些奇怪的原因,“幼稚”的实现比“更智能”的解决方案运行得更快。

难道(xelem"aeiouyAEIOUY" && c == x) 没有被评估,因为 Haskell 是惰性的,所以一旦达到基本情况,所有的 thunk 都会被评估,从而导致比“幼稚”实现慢?

如果是这样,那为什么是这样,当我将(xelem"aeiouyAEIOUY" && c == x)放入if语句中强制评估时,函数的性能是相似的?

我是否也可以说第二个函数具有O(1) 空间使用和O(n) 时间?

【问题讨论】:

  • Maybe String -> Bool 的签名在这里很奇怪。你为什么不只拥有String -> Bool 而忘记Nothing 案例?
  • 啊,我只是在试验 Maybe 类型类,真的没必要。

标签: performance haskell lazy-evaluation


【解决方案1】:

最简单的解决方法是颠倒您第一次调用&& 的顺序,在昂贵的elem 检查之前执行廉价的== 检查。

这将使您的函数运行得更快......但它仍然是不正确的!您基本上是在断言,在第一个元音之后,所有后续字符都是完全相同的元音;相反,您打算做的是断言所有以下 元音 都是相同的元音。我会这样写:

(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")

【讨论】:

  • 奇怪的是,即使进行了快速更改,它的运行速度仍然比第一个函数慢。我的测试用例是最坏的情况:singleVowelFaster $ Just $ take 100000 $ repeat 'a'
  • 嗯?你从未展示过singleVowelFaster 的任何来源,也从未展示过任何与该类型签名匹配的函数。如果你以这种方式调用你的函数,它的类型必须是singleVowelFaster :: Maybe [Char] -> Bool,这根本没有意义。请发布您正在运行的实际代码。
  • 糟糕,我的错。更新了上面的问题。
猜你喜欢
  • 2016-04-10
  • 2013-11-26
  • 1970-01-01
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多