efficiently checking that all the elements of a (big) list are the same

前端 未结 9 1036
清酒与你
清酒与你 2020-12-14 16:01

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 wi

相关标签:
9条回答
  • 2020-12-14 16:47

    While not very efficient (it will traverse the whole list even if the first two elements don't match), here's a cheeky solution:

    import Data.List (group)
    
    allTheSame :: (Eq a) => [a] -> Bool
    allTheSame = (== 1) . length . group
    

    Just for fun.

    0 讨论(0)
  • 2020-12-14 16:51

    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 before and 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 before and 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 the head 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.

    0 讨论(0)
  • 2020-12-14 16:54

    Here is another version (don't need to traverse whole list in case something doesn't match):

    allTheSame [] = True
    allTheSame (x:xs) = isNothing $ find (x /= ) xs
    

    This may not be syntactically correct , but I hope you got the idea.

    0 讨论(0)
提交回复
热议问题