好的,基本上这是您真正需要sort 以提高效率的罕见情况之一。事实上,Data.List.Unique 包有一个 repeated 函数专门用于这项工作,如果检查源,可以看到选择了 sort 和 group 策略。我想这不是最有效的算法。我将讨论如何让sort 更有效率,但暂时让我们享受一下,因为这是一个很好的问题。
所以我们在Data.List 包中有tails :: [a] -> [[a]] 函数。相应地;
*Main> tails [3,3,6,1]
[[3,3,6,1],[3,6,1],[6,1],[1],[]]
您可能很快注意到,我们可以通过应用一个函数来检查all 项目是否不同,从而将tails 的tails 列表tail 的tail [[3,6,1],[6,1],[1],[]] 与给定的原始列表一起使用。这个函数可以是一个列表推导或者只是all :: Foldable t => (a -> Bool) -> t a -> Bool 函数。问题是,我想把zipWith 短路,这样一旦我遇到第一个骗子,让我们停止zipWith 通过检查其余部分来做浪费工作。为此,我可以使用zipWith 的一元版本,即zipWithM :: Applicative m => (a -> b -> m c) -> [a] -> [b] -> m [c],它位于Control.Monad 包中。原因是,从它的类型签名我们了解到,如果我的 monad 恰好是 Maybe 或 Either,则当它在中间考虑 Nothing 或 Left whatever 时,它将停止进一步计算。
哦..!在 Haskell 中,我也喜欢使用 bool :: a -> a -> Bool -> a 函数而不是 if 和 then。 bool 是 Haskell 的三元运算,类似于
bool "work time" "coffee break" isCoffeeTime
否定的选择在左边,肯定的选择在右边,其中isCoffeeTime :: Bool 是一个函数,如果是咖啡时间则返回True。也非常可组合.. 太酷了..!
既然我们现在已经掌握了所有的背景知识,我们就可以继续编写代码了
import Control.Monad (zipWithM)
import Data.List (tails)
import Data.Bool (bool)
anyDupe :: Eq a => [a] -> Either a [a]
anyDupe xs = zipWithM f xs ts
where ts = tail $ tails xs
f = \x t -> bool (Left x) (Right x) $ all (x /=) t
*Main> anyDupe [1,2,3,4,5]
Right [1,2,3,4,5] -- no dupes so we get the `Right` with the original list
*Main> anyDupe [3,3,6,1]
Left 3 -- here we have the first duplicate since zipWithM short circuits.
*Main> anyDupe $ 10^7:[1..10^7]
Left 10000000 -- wow zipWithM worked and returned reasonably fast.
但是再次......正如我所说,这仍然是一种幼稚的方法,因为理论上我们正在执行n(n+1)/2 操作。是的 zipWithM 如果第一个遇到的骗子靠近头部,则大大减少了冗余,但这个算法仍然是 O(n^2)。
我相信在这种特殊情况下最好使用 Haskell 的天堂 sort 算法(顺便说一下,这不是我们所知道的归并排序)。
现在算法奖颁给了 -> 这里的鼓声 -> sort 和 fold -> 掌声。抱歉没有分组。
所以现在......我们将再次使用一元技巧来利用短路。我们将使用foldM :: (Foldable t, Monad m) => (b -> a -> m b) -> b -> t a -> m b。当与Either monad 一起使用时,这也允许我们返回更有意义的结果。好的,让我们一起做。任何Left n 表示n 是第一个欺骗,不再进行计算,而任何Right _ 表示没有欺骗。
import Control.Monad (foldM)
import Data.List (sort)
import Data.Bool (bool)
anyDupe' :: (Eq a, Ord a, Enum a) => [a] -> Either a a
anyDupe' xs = foldM f i $ sort xs
where i = succ $ head xs -- prevent the initial value to be equal with the value at the head
f = \b a -> bool (Left a) (Right a) (a /= b)
*Main> anyDupe' [1,2,3,4,5]
Right 5
*Main> anyDupe' [3,3,6,1]
Left 3
*Main> anyDupe' $ 1:[10^7,(10^7-1)..1]
Left 1
(2.97 secs, 1,040,110,448 bytes)
*Main> anyDupe $ 1:[10^7,(10^7-1)..1]
Left 1
(2.94 secs, 1,440,112,888 bytes)
*Main> anyDupe' $ [1..10^7]++[10^7]
Left 10000000
(5.71 secs, 3,600,116,808 bytes) -- winner by far
*Main> anyDupe $ [1..10^7]++[10^7] -- don't try at home, it's waste of energy
在现实世界的场景中,anyDupe' 应该永远是赢家。