有效地检查(大)列表的所有元素是否相同

问题

让我们假设我们有一个列表xs(可能是一个很大的),我们想检查它的所有元素是否相同。

我想出了各种想法:

解决方案0

检查尾部xs中的所有元素是否等于头xs:

allTheSame :: (Eq a) => [a] -> Bool
allTheSame xs = and $ map (== head xs) (tail xs)

解决方案1

检查长度xs等于通过在等于头xs时从xs获取元素获得的列表的长度

allTheSame' :: (Eq a) => [a] -> Bool
allTheSame' xs = (length xs) == (length $ takeWhile (== head xs) xs)

解决方案2

递归解决方案:如果xs的前两个元素相等,allTheSame返回True,并且allTheSame在其余的xs上返回True

allTheSame'' :: (Eq a) => [a] -> Bool
allTheSame'' xs
  | n == 0 = False
  | n == 1 = True
  | n == 2 = xs !! 0 == xs !! 1
  | otherwise = (xs !! 0 == xs !! 1) && (allTheSame'' $ snd $ splitAt 2 xs)
    where  n = length xs

解决方案3

分手征服:

allTheSame''' :: (Eq a) => [a] -> Bool
allTheSame''' xs
  | n == 0 = False
  | n == 1 = True
  | n == 2 = xs !! 0 == xs !! 1
  | n == 3 = xs !! 0 == xs !! 1 && xs !! 1 == xs !! 2
  | otherwise = allTheSame''' (fst split) && allTheSame''' (snd split)
    where n = length xs
          split = splitAt (n `div` 2) xs

解决方案4

我在写这个问题时就想到了:

allTheSame'''' :: (Eq a) => [a] -> Bool
allTheSame'''' xs = all (== head xs) (tail xs)

问题

>我认为解决方案0不是非常有效率,至少在内存方面,因为地图将在应用之前构造另一个列表和其元素。我对吗?
>解决方案1仍然不是非常有效率,至少在内存方面,因为takeWhile将再次创建一个附加列表。我对吗?
>解决方案2是尾递归(右?),它应该是非常有效的,因为它将返回False,一旦(xs !! 0 == xs !! 1)为False。我对吗?
>解决方案3应该是最好的,因为它的复杂度应该是O(log n)
>解决方案4对我来说看起来非常Haskellish(是吗?),但它可能与解决方案0相同,因为所有的p =和。地图p(来自Prelude.hs)。我对吗?
>还有其他更好的写作方式吗?现在,我希望有人会回答这个问题,告诉我有一个内置的功能:我已经用hoogle搜索,我还没有找到它。无论如何,由于我正在学习Haskell,我相信这是一个很好的锻炼我:)

欢迎任何其他评论。谢谢!

gatoatigrado的答案为衡量各种解决方案的性能提供了一些不错的建议。这是一个更象征性的答案。

我认为解决方案0(或恰恰等价于解决方案4)将是最快的。记住,Haskell是懒惰的,所以地图将不必在之前构建整个列表并被应用。建立直觉的一个好方法是玩无限远。例如:

ghci> and $ map (< 1000) [1..]
False

这询问所有数字是否小于1,000。如果地图构建了整个列表并被应用,那么这个问题永远不会被回答。即使你给列表一个非常大的右端点(也就是说,Haskell没有做任何“魔术”,取决于列表是无限的),表达式仍然会很快回复。

为了开始我的例子,让我们使用这些定义:

and [] = True
and (x:xs) = x && and xs

map f [] = []
map f (x:xs) = f x : map f xs

True && x = x
False && x = False

以下是AllSame的评估顺序[7,7,7,7,8,7,7,7]。会有额外的分享,写下来太痛苦了。我也会早于评估头脑表达,而不是简单(它将被评估,所以它几乎不同)。

allTheSame [7,7,7,7,8,7,7,7]
allTheSame (7:7:7:7:8:7:7:7:[])
and $ map (== head (7:7:7:7:8:7:7:7:[])) (tail (7:7:7:7:8:7:7:7:[]))
and $ map (== 7)  (tail (7:7:7:7:8:7:7:7:[]))
and $ map (== 7)          (7:7:7:8:7:7:7:[])
and $ (== 7) 7 : map (== 7) (7:7:8:7:7:7:[])
(== 7) 7 && and (map (== 7) (7:7:8:7:7:7:[]))
True     && and (map (== 7) (7:7:8:7:7:7:[]))
            and (map (== 7) (7:7:8:7:7:7:[]))
(== 7) 7 && and (map (== 7)   (7:8:7:7:7:[]))
True     && and (map (== 7)   (7:8:7:7:7:[]))
            and (map (== 7)   (7:8:7:7:7:[]))
(== 7) 7 && and (map (== 7)     (8:7:7:7:[]))
True     && and (map (== 7)     (8:7:7:7:[]))
            and (map (== 7)     (8:7:7:7:[]))
(== 7) 8 && and (map (== 7)       (7:7:7:[]))
False    && and (map (== 7)       (7:7:7:[]))
False

看看我们甚至没有检查最后3 7的?这是一个懒惰的评估,使列表工作更像一个循环。所有其他解决方案都使用诸如长度的昂贵功能(必须一直走到列表的末尾来给出答案),所以它们的效率会降低,而且它们也不会在无限列表上工作。在无限列表中工作,并且有效地经常在Haskell中一起工作。

http://stackoverflow.com/questions/6121256/efficiently-checking-that-all-the-elements-of-a-big-list-are-the-same

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:有效地检查(大)列表的所有元素是否相同