有效地检查(大)列表中的所有元素是否相同 [英] efficiently checking that all the elements of a (big) list are the same
问题描述
问题
让我们假设我们有一个列表 xs
(可能是一个很大的列表),并且我们想检查它的所有元素是否相同。
我提出了各种想法:
解决方案0
检查 tail xs
中的所有元素等于 head xs
:
allTheSame ::(Eq a)=> [a] - > Bool
allTheSame xs = and $ map(== head xs)(tail xs)
解决方案1检查长度xs
是否等于通过从<$ c获取元素获得的列表长度
$ c> xs ,而它们等于头部xs
allTheSame'::(等式a)=> [a] - > Bool
allTheSame'xs =(length xs)==(length $ takeWhile(== head xs)xs)
解决方案2
递归解决方案: allTheSame
返回 code>如果
xs
的前两个元素相等并且 allTheSame
返回 True
xs
allTheSame ''::(等式a)=> [a] - > Bool
allTheSame''xs
| n == 0 = False
| n == 1 = True
| n == 2 = xs !! 0 == xs !! 1
|否则=(xs !! 0 == xs !! 1)&& (allTheSame''$ snd $ splitAt 2 xs)
其中n =长度xs
解决方案3
分而治之:
allTheSame''':: (等式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
|否则= allTheSame'''(fst split)&& allTheSame'''(snd split)
其中n =长度xs
split = splitAt(n`div` 2)xs
解决方案4
我在写这个问题时想到了这个问题:
allTheSame''''::(方程a)=> [a] - > Bool
allTheSame''''xs = all(== head xs)(tail xs)
< h2>问题
-
我认为解决方案0效率不高,至少在内存方面,因为
map
会在将和
应用到其元素之前构造另一个列表。我对吗?
-
解决方案1仍然效率不高,至少在内存方面,因为
takeWhile
会再次建立一个额外的列表。解决方案2是尾递归(right?),它应该非常高效,因为它会返回False 尽快
(xs !! 0 == xs !! 1)
为False。我对吗?
解决方案3应该是最好的,因为它的复杂性应该是O(log n)
解决方案4对我来说看起来相当Haskellish(是吗?),但它可能与解决方案0相同,因为
所有p =和。 map p
(来自Prelude.hs)。对吗?
还有其他更好的书写方式吗?
allTheSame
?现在,我希望有人会回答这个问题,告诉我这里有一个内置函数:我用hoogle进行搜索,但我没有找到它。无论如何,因为我正在学习Haskell,所以我认为这对我来说是一个很好的练习:)
其他欢迎发表评论。谢谢!
解决方案gatoatigrado的答案为衡量各种解决方案的性能提供了一些不错的建议。这是一个更具象征意义的答案。
我认为解决方案0(或者,完全等同于解决方案4)将是最快的。请记住,Haskell是 lazy ,所以
map
不需要在和
应用。建立直觉的一个好方法就是玩无限。举例来说:
ghci>和$ map(< 1000)[1 ..]
False
所有数字都小于1,000。如果
map
在应用和
之前构造了整个列表,那么这个问题就永远无法回答。即使给列表一个非常大的右端点(即Haskell没有做任何魔术,这取决于列表是否无限),表达式仍然会很快回应。
$ b和[] = True
和(x: xs)= x&&和xs
map f [] = []
map f(x:xs)= f x:map f xs
True&& x = x
False&& x = False
以下是
allTheSame [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:[]))
和$ map(== 7)(tail(7:7:7:7:8:7:7:7:[]))
和$ map(== 7)(7:7:7:8:7:7:7:[])
和$(== 7)7:map(== 7)(7: 7:8:7:7:7:[])
(== 7)7&&和(map(== 7)(7:7:8:7:7:7:[]))
True&&和(map(== 7)(7:7:8:7:7:7:[]))
和(map(== 7)(7:7:8:7:7:7: []))
(== 7)7&&和(map(== 7)(7:8:7:7:7:[]))
True&&和(map(== 7)(7:8:7:7:7:[]))
和(map(== 7)(7:8:7:7:7:[]))
(== 7)7&&和(map(== 7)(8:7:7:7:[]))
True&&和(map(== 7)(8:7:7:7:[]))
和(map(== 7)(8:7:7:7:[]))
(== 7)8&&和(map(== 7)(7:7:7:[]))
False&&和(map(== 7)(7:7:7:[]))
False
看看我们甚至不需要检查最后3个7的?这是一个懒惰的评估,使得列表更像一个循环。所有其他解决方案都使用昂贵的函数,如length
(它们必须一直走到列表的最后才能给出答案),所以它们的效率会更低,他们将无法在无限列表上工作。在无限的列表上工作并且经常在Haskell中一起工作。Problem
Let us suppose that we have a list
xs
(possibly a very big one), and we want to check that all its elements are the same.I came up with various ideas:
Solution 0
checking that all elements in
tail xs
are equal tohead xs
:allTheSame :: (Eq a) => [a] -> Bool allTheSame xs = and $ map (== head xs) (tail xs)
Solution 1
checking that
length xs
is equal to the length of the list obtained by taking elements fromxs
while they're equal tohead xs
allTheSame' :: (Eq a) => [a] -> Bool allTheSame' xs = (length xs) == (length $ takeWhile (== head xs) xs)
Solution 2
recursive solution:
allTheSame
returnsTrue
if the first two elements ofxs
are equal andallTheSame
returnsTrue
on the rest ofxs
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
Solution 3
divide and conquer:
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
Solution 4
I just thought about this while writing this question:
allTheSame'''' :: (Eq a) => [a] -> Bool allTheSame'''' xs = all (== head xs) (tail xs)
Questions
I think Solution 0 is not very efficient, at least in terms of memory, because
map
will construct another list before applyingand
to its elements. Am I right?Solution 1 is still not very efficient, at least in terms of memory, because
takeWhile
will again build an additional list. Am I right?Solution 2 is tail recursive (right?), and it should be pretty efficient, because it will return
False
as soon as(xs !! 0 == xs !! 1)
is False. Am I right?Solution 3 should be the best one, because it complexity should be O(log n)
Solution 4 looks quite Haskellish to me (is it?), but it's probably the same as Solution 0, because
all p = and . map p
(from Prelude.hs). Am I right?Are there other better ways of writing
allTheSame
? Now, I expect someone will answer this question telling me that there's a build-in function that does this: I've searched with hoogle and I haven't found it. Anyway, since I'm learning Haskell, I believe that this was a good exercise for me :)
Any other comment is welcome. Thank you!
解决方案gatoatigrado's answer gives some nice advice for measuring the performance of various solutions. Here is a more symbolic answer.
I think solution 0 (or, exactly equivalently, solution 4) will be the fastest. Remember that Haskell is lazy, so
map
will not have to construct the whole list beforeand
is applied. A good way to build intuition about this is to play with infinity. So for example:ghci> and $ map (< 1000) [1..] False
This asks whether all numbers are less than 1,000. If
map
constructed the entire list beforeand
were applied, then this question could never be answered. The expression will still answer quickly even if you give the list a very large right endpoint (that is, Haskell is not doing any "magic" depending on whether a list is infinite).To start my example, let's use these definitions:
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
Here is the evaluation order for
allTheSame [7,7,7,7,8,7,7,7]
. There will be extra sharing that is too much of a pain to write down. I will also evaluate thehead
expression earlier than it would be for conciseness (it would have been evaluated anyway, so it's hardly different).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
See how we didn't even have to check the last 3 7's? This is lazy evaluation making a list work more like a loop. All your other solutions use expensive functions like
length
(which have to walk all the way to the end of the list to give an answer), so they will be less efficient and also they will not work on infinite lists. Working on infinite lists and being efficient often go together in Haskell.这篇关于有效地检查(大)列表中的所有元素是否相同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!