web-dev-qa-db-fra.com

vérifier efficacement que tous les éléments d'une (grande) liste sont les mêmes

Problème

Supposons que nous ayons une liste xs (éventuellement une très grande) et que nous voulions vérifier que tous ses éléments sont identiques.

Je suis venu avec diverses idées:

Solution 0

vérifier que tous les éléments dans tail xs sont égaux à head xs:

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

Solution 1

vérifier que length xs est égal à la longueur de la liste obtenue en prenant des éléments de xs alors qu'ils sont égaux à head xs

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

Solution 2

solution récursive: allTheSame renvoie True si les deux premiers éléments de xs sont égaux et allTheSame renvoie True sur le reste de xs

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

diviser et conquérir:

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

Je viens de penser à cela en écrivant cette question:

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

Des questions

  1. Je pense que la solution 0 n’est pas très efficace, du moins en termes de mémoire, car map construira une autre liste avant d’appliquer and à ses éléments. Ai-je raison?

  2. La solution 1 n'est toujours pas très efficace, du moins en termes de mémoire, car takeWhile construira à nouveau une liste supplémentaire. Ai-je raison?

  3. La solution 2 est récursive (non?), Et elle devrait être assez efficace, car elle retournera False dès que (xs !! 0 == xs !! 1) sera faux. Ai-je raison?

  4. La solution 3 devrait être la meilleure, car sa complexité devrait être O (log n)

  5. La solution 4 me semble assez haskellish (est-ce que c'est?), Mais c'est probablement la même chose que la solution 0, car all p = and . map p (de Prelude.hs). Ai-je raison?

  6. Y a-t-il d'autres façons d'écrire allTheSame? Maintenant, je suppose que quelqu'un répondra à cette question en me disant qu'il existe une fonction intégrée qui fait ceci: j'ai effectué une recherche avec hoogle et je ne l'ai pas trouvée. Quoi qu'il en soit, depuis que je suis en train d'apprendre Haskell, je pense que c'était un bon exercice pour moi :)

Tout autre commentaire est le bienvenu. Je vous remercie!

28
MarcoS

la réponse de gatoatigrado donne quelques conseils judicieux pour mesurer les performances de diverses solutions. Voici une réponse plus symbolique.

Je pense que la solution 0 (ou exactement de la même manière, solution 4) sera la plus rapide. N'oubliez pas que Haskell est paresseux _, donc map n'aura pas à construire la liste complète avant que and ne soit appliqué. Un bon moyen de construire une intuition à ce sujet est de jouer à l'infini. Donc par exemple:

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

Ceci demande si tous les nombres sont inférieurs à 1 000. Si map a construit la liste complète avant que and ne soit appliqué, alors on ne pourra jamais répondre à cette question. L’expression répondra toujours rapidement même si vous attribuez à la liste un très grand point final droit (c’est-à-dire que Haskell ne fait aucune "magie" selon que la liste est infinie).

Pour commencer mon exemple, utilisons ces définitions:

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

Voici l'ordre d'évaluation pour allTheSame [7,7,7,7,8,7,7,7]. Il y aura des partages supplémentaires qui sont trop pénibles à écrire. J'évaluerai également l'expression head plus tôt que pour la concision (elle aurait été évaluée de toute façon, donc ce n'est pas très différent).

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

Voyez comment nous n'avons même pas eu à vérifier les 3 7 derniers? C'est une évaluation paresseuse qui fait qu'une liste fonctionne plus comme une boucle. Toutes vos autres solutions utilisent des fonctions coûteuses comme length (qui doivent aller jusqu'au bout de la liste pour donner une réponse), elles seront donc moins efficaces et ne fonctionneront pas sur des listes infinies. Travailler sur des listes infinies et être efficace vont souvent de pair en Haskell.

28
luqui

Tout d’abord, je ne pense pas que vous souhaitiez travailler avec des listes. Beaucoup de vos algorithmes reposent sur le calcul de la longueur, ce qui est mauvais. Vous voudrez peut-être considérer le paquet vector , qui vous donnera la longueur de O(1) par rapport à O(n) pour une liste. Les vecteurs sont également beaucoup plus efficaces en termes de mémoire, en particulier si vous pouvez utiliser des variantes sans boîte ou stockables.

Cela étant dit, vous devez vraiment prendre en compte les parcours et les modèles d'utilisation dans votre code. Les listes de Haskell sont très efficaces si elles peuvent être générées à la demande et consommées une fois. Cela signifie que vous ne devez pas conserver les références à une liste. Quelque chose comme ça:

average xs = sum xs / length xs

exige que toute la liste soit conservée en mémoire (par sum ou length) jusqu'à ce que les deux parcours soient terminés. Si vous pouvez parcourir votre liste en une étape, ce sera beaucoup plus efficace.

Bien sûr, vous devrez peut-être quand même conserver la liste, par exemple pour vérifier si tous les éléments sont égaux, et s'ils ne le sont pas, faites autre chose avec les données. Dans ce cas, avec des listes de toute taille, il vaut probablement mieux utiliser une structure de données plus compacte (par exemple, un vecteur).

Maintenant que cela est hors de propos, voici un aperçu de chacune de ces fonctions. Où je montre le noyau, il a été généré avec ghc-7.0.3 -O -ddump-simpl. En outre, ne vous souciez pas de juger les performances du code Haskell lors de la compilation avec -O0. Compilez-le avec les indicateurs que vous utiliseriez réellement pour le code de production, généralement au moins -O et peut-être aussi d'autres options.

Solution 0

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

GHC produit ce noyau:

Test.allTheSame
  :: forall a_abG. GHC.Classes.Eq a_abG => [a_abG] -> GHC.Bool.Bool
[GblId,
 Arity=2,
 Str=DmdType LS,
 Unf=Unf{Src=<Vanilla>, TopLvl=True, Arity=2, Value=True,
         ConLike=True, Cheap=True, Expandable=True,
         Guidance=IF_ARGS [3 3] 16 0}]
Test.allTheSame =
  \ (@ a_awM)
    ($dEq_awN :: GHC.Classes.Eq a_awM)
    (xs_abH :: [a_awM]) ->
    case xs_abH of _ {
      [] ->
        GHC.List.tail1
        `cast` (CoUnsafe (forall a1_axH. [a1_axH]) GHC.Bool.Bool
                :: (forall a1_axH. [a1_axH]) ~ GHC.Bool.Bool);
      : ds1_axJ xs1_axK ->
        letrec {
          go_sDv [Occ=LoopBreaker] :: [a_awM] -> GHC.Bool.Bool
          [LclId, Arity=1, Str=DmdType S]
          go_sDv =
            \ (ds_azk :: [a_awM]) ->
              case ds_azk of _ {
                [] -> GHC.Bool.True;
                : y_azp ys_azq ->
                  case GHC.Classes.== @ a_awM $dEq_awN y_azp ds1_axJ of _ {
                    GHC.Bool.False -> GHC.Bool.False; GHC.Bool.True -> go_sDv ys_azq
                  }
              }; } in
        go_sDv xs1_axK
    }

Cela semble plutôt bien, en fait. Cela produira une erreur avec une liste vide, mais cela est facilement corrigé. C'est le case xs_abH of _ { [] ->. Une fois que GHC a effectué une transformation worker/wrapper, la fonction de travail récursif est la liaison letrec { go_sDv. Le travailleur examine son argument. Si [], il est arrivé à la fin de la liste et renvoie True. Sinon, il compare la tête du reste au premier élément et retourne False ou vérifie le reste de la liste.

Trois autres fonctionnalités.

  1. La map a été entièrement fusionnée Et n'attribue pas de liste temporaire .
  2. Près du haut de la définition , Notez l'instruction Cheap=True. Cela signifie que GHC considère que la fonction Est "peu coûteuse" et constitue donc un candidat Pour la réinsertion en ligne. Sur un site d'appel , Si un type d'argument concret Peut être déterminé, GHC probablement Inline allTheSame et produira une boucle interne Très serrée, complètement en ignorant la recherche dans le dictionnaire Eq .
  3. La fonction worker est Queue-récursive.

Verdict: très fort candidat.

Solution 1

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

Même sans regarder au cœur, je sais que ce ne sera pas aussi bon. La liste est parcourue plus d'une fois, d'abord par length xs, puis par length $ takeWhile. Non seulement vous avez le surcoût supplémentaire de plusieurs traversées, mais cela signifie également que la liste doit être conservée en mémoire après la première traversée et ne peut pas être enregistrée par GC. Pour une grande liste, c'est un problème grave.

Test.allTheSame'
  :: forall a_abF. GHC.Classes.Eq a_abF => [a_abF] -> GHC.Bool.Bool
[GblId,
 Arity=2,
 Str=DmdType LS,
 Unf=Unf{Src=<Vanilla>, TopLvl=True, Arity=2, Value=True,
         ConLike=True, Cheap=True, Expandable=True,
         Guidance=IF_ARGS [3 3] 20 0}]
Test.allTheSame' =
  \ (@ a_awF)
    ($dEq_awG :: GHC.Classes.Eq a_awF)
    (xs_abI :: [a_awF]) ->
    case GHC.List.$wlen @ a_awF xs_abI 0 of ww_aC6 { __DEFAULT ->
    case GHC.List.$wlen
           @ a_awF
           (GHC.List.takeWhile
              @ a_awF
              (let {
                 ds_sDq :: a_awF
                 [LclId, Str=DmdType]
                 ds_sDq =
                   case xs_abI of _ {
                     [] -> GHC.List.badHead @ a_awF; : x_axk ds1_axl -> x_axk
                   } } in
               \ (ds1_dxa :: a_awF) ->
                 GHC.Classes.== @ a_awF $dEq_awG ds1_dxa ds_sDq)
              xs_abI)
           0
    of ww1_XCn { __DEFAULT ->
    GHC.Prim.==# ww_aC6 ww1_XCn
    }
    }

Regarder le noyau ne dit pas grand chose au-delà de ça. Cependant, notez ces lignes:

case GHC.List.$wlen @ a_awF xs_abI 0 of ww_aC6 { __DEFAULT ->
        case GHC.List.$wlen

C'est ici que se produisent les traversées de liste. Le premier obtient la longueur de la liste externe et la lie à ww_aC6. La seconde obtient la longueur de la liste interne, mais la liaison n’est pas terminée avant le bas, à

of ww1_XCn { __DEFAULT ->
GHC.Prim.==# ww_aC6 ww1_XCn

Les longueurs (les deux Ints) peuvent être décompressées et comparées par un primop, mais c'est une petite consolation après la surcharge introduite.

Verdict: Pas bien.

Solution 2

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

Cela pose le même problème que la solution 1. La liste est parcourue plusieurs fois et ne peut pas être lue par GC. C'est pire ici cependant, parce que maintenant la longueur est calculée pour chaque sous-liste. Je m'attendrais à ce que cette performance soit la pire de toutes sur les listes de toutes tailles. De plus, pourquoi créez-vous des listes de 1 et 2 éléments lorsque vous vous attendez à ce que la liste soit volumineuse?

Verdict: n'y pense même pas.

Solution 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

Cela pose le même problème que la solution 2. Notamment, la liste est parcourue plusieurs fois par length. Je ne suis pas certain qu'une approche «diviser pour régner» soit un bon choix pour résoudre ce problème, cela pourrait prendre plus de temps qu'une simple analyse. Cela dépendrait des données et mériterait d’être testé.

Verdict: Peut-être, si vous utilisiez une structure de données différente.

Solution 4

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

C'était fondamentalement ma première pensée. Vérifions à nouveau le noyau.

Test.allTheSame''''
  :: forall a_abC. GHC.Classes.Eq a_abC => [a_abC] -> GHC.Bool.Bool
[GblId,
 Arity=2,
 Str=DmdType LS,
 Unf=Unf{Src=<Vanilla>, TopLvl=True, Arity=2, Value=True,
         ConLike=True, Cheap=True, Expandable=True,
         Guidance=IF_ARGS [3 3] 10 0}]
Test.allTheSame'''' =
  \ (@ a_am5)
    ($dEq_am6 :: GHC.Classes.Eq a_am5)
    (xs_alK :: [a_am5]) ->
    case xs_alK of _ {
      [] ->
        GHC.List.tail1
        `cast` (CoUnsafe (forall a1_axH. [a1_axH]) GHC.Bool.Bool
                :: (forall a1_axH. [a1_axH]) ~ GHC.Bool.Bool);
      : ds1_axJ xs1_axK ->
        GHC.List.all
          @ a_am5
          (\ (ds_dwU :: a_am5) ->
             GHC.Classes.== @ a_am5 $dEq_am6 ds_dwU ds1_axJ)
          xs1_axK
    }

Ok pas trop mal Comme la solution 1, cette erreur se produira sur les listes vides. Le parcours de la liste est caché dans GHC.List.all, mais il sera probablement étendu au bon code sur un site d’appel.

Verdict: Un autre concurrent fort.

Donc, entre tous ces éléments, avec les listes, je suppose que les solutions 0 et 4 sont les seules qui valent la peine d’être utilisées, et qu’elles sont à peu près les mêmes. Je pourrais envisager l'option 3 dans certains cas.

Éditer: dans les deux cas, les erreurs sur les listes vides peuvent être simplement corrigées comme dans la réponse de @ augustss.

La prochaine étape serait de faire un peu de temps profilage avec critère .

20
John L

Une solution utilisant des paires consécutives:

allTheSame xs = and $ zipWith (==) xs (tail xs)
12
tokland

Q1 - Oui, je pense que votre solution simple convient, il n’ya pas de fuite de mémoire. Q4 - La solution 3 n’est pas log (n), via l’argument très simple selon lequel vous devez examiner tous les éléments de la liste pour déterminer s’ils sont identiques, et examiner un élément prend un pas de temps. Q5 - oui. Q6, voir ci-dessous.

La façon de s'y prendre est de le taper et de l'exécuter

main = do
    print $ allTheSame (replicate 100000000 1)

puis exécutez ghc -O3 -optc-O3 --make Main.hs && time ./Main. J'aime mieux la dernière solution (vous pouvez également utiliser la correspondance des motifs pour la nettoyer un peu),

allTheSame (x:xs) = all (==x) xs

Ouvrez ghci et lancez ": step fcn" sur ces choses. Cela vous en apprendra beaucoup sur le développement de l’évaluation paresseuse. En général, lorsque vous faites correspondre un constructeur, par exemple "x: xs", c'est le temps constant. Lorsque vous appelez "length", Haskell doit calculer tous les éléments de la liste (bien que leurs valeurs restent "à calculer"). Les solutions 1 et 2 sont donc mauvaises.

modifier 1

Désolé si ma réponse précédente était un peu superficielle. Il semble que le fait d’élargir les choses manuellement aide un peu (bien que comparé aux autres options, c’est une amélioration triviale),

{-# LANGUAGE BangPatterns #-}
allTheSame [] = True
allTheSame ((!x):xs) = go x xs where
    go !x [] = True
    go !x (!y:ys) = (x == y) && (go x ys)

Il semble que ghc spécialise déjà la fonction, mais vous pouvez aussi regarder le pragma specialize, au cas où cela ne fonctionnerait pas pour votre code [ link ].

6
gatoatigrado

Voici une autre version (il n'est pas nécessaire de parcourir toute la liste au cas où quelque chose ne correspond pas):

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

Cela n’est peut-être pas correct du point de vue syntaxique, mais j’espère que vous avez eu l’idée.

4
Ankur

Voici une autre façon amusante:

{-# INLINABLE allSame #-}
allSame :: Eq a => [a] -> Bool
allSame xs = foldr go (`seq` True) xs Nothing where
  go x r Nothing = r (Just x)
  go x r (Just prev) = x == prev && r (Just x)

En gardant une trace de l'élément précédent, plutôt que du premier, cette implémentation peut facilement être modifiée pour implémenter increasing ou decreasing. Pour tous les comparer au premier, vous pouvez renommer prev en first et remplacer Just x par Just first.


Comment cela sera-t-il optimisé? Je n'ai pas vérifié en détail, mais je vais raconter une bonne histoire en fonction de certaines choses que je connais sur les optimisations de GHC. 

Supposons d'abord que la fusion de liste ne se produise pas. Alors foldr sera en ligne, donnant quelque chose comme

allSame xs = allSame' xs Nothing where
  allSame' [] = (`seq` True)
  allSame' (x : xs) = go x (allSame' xs)

Eta expansion donne alors

allSame' [] acc = acc `seq` True
allSame' (x : xs) acc = go x (allSame' xs) acc

Inline go,

allSame' [] acc = acc `seq` True
allSame' (x : xs) Nothing = allSame' xs (Just x)
allSame' (x : xs) (Just prev) =
  x == prev && allSame' xs (Just x)

Maintenant, GHC peut reconnaître que la valeur Maybe est toujours Just sur l'appel récursif et utiliser une transformation worker-wrapper pour en tirer parti:

allSame' [] acc = acc `seq` True
allSame' (x : xs) Nothing = allSame'' xs x
allSame' (x : xs) (Just prev) = x == prev && allSame'' xs x

allSame'' [] prev = True
allSame'' (x : xs) prev = x == prev && allSame'' xs x

Rappelez-vous maintenant que

allSame xs = allSame' xs Nothing

et allSame' n'est plus récursif, il peut donc être réduit en bêta:

allSame [] = True
allSame (x : xs) = allSame'' xs x

allSame'' [] _ = True
allSame'' (x : xs) prev = x == prev && allSame'' xs x

Le code d'ordre supérieur est donc devenu un code récursif efficace sans allocation supplémentaire.

La compilation du module définissant allSame à l'aide de -O2 -ddump-simpl -dsuppress-all -dno-suppress-type-signatures donne les résultats suivants (je l'ai un peu nettoyé):

allSame :: forall a. Eq a => [a] -> Bool
allSame =
  \ (@ a) ($dEq_a :: Eq a) (xs0 :: [a]) ->
    let {
      equal :: a -> a -> Bool
      equal = == $dEq_a } in
    letrec {
      go :: [a] -> a -> Bool
      go =
        \ (xs :: [a]) (prev :: a) ->
          case xs of _ {
            [] -> True;
            : y ys ->
              case equal y prev of _ {
                False -> False;
                True -> go ys y
              }
          }; } in
    case xs0 of _ {
      [] -> True;
      : x xs -> go xs x
    }

Comme vous pouvez le constater, il s’agit essentiellement du résultat que j’ai décrit. Le bit equal = == $dEq_a est l'endroit où la méthode d'égalité est extraite du dictionnaire Eq et enregistrée dans une variable. Elle ne doit donc être extraite qu'une fois.


Et si la fusion de liste se produit se produit? Voici un rappel de la définition:

allSame xs = foldr go (`seq` True) xs Nothing where
  go x r Nothing = r (Just x)
  go x r (Just prev) = x == prev && r (Just x)

Si nous appelons allSame (build g), la foldr fusionnera avec la build conformément à la règle foldr c n (build g) = g c n, ce qui donnera

allSame (build g) = g go (`seq` True) Nothing

Cela ne nous rend nulle part intéressant à moins que g soit connu. Alors choisissons quelque chose de simple:

replicate k0 a = build $ \c n ->
  let
    rep 0 = n
    rep k = a `c` rep (k - 1)
  in rep k0

Donc, si h = allSame (replicate k0 a), h devient

let
  rep 0 = (`seq` True)
  rep k = go a (rep (k - 1))
in rep k0 Nothing

Eta en expansion,

let
  rep 0 acc = acc `seq` True
  rep k acc = go a (rep (k - 1)) acc
in rep k0 Nothing

Inline go,

let
  rep 0 acc = acc `seq` True
  rep k Nothing = rep (k - 1) (Just a)
  rep k (Just prev) = a == prev && rep (k - 1) (Just a)
in rep k0 Nothing

A nouveau, GHC peut voir que l'appel récursif est toujours Just, donc

let
  rep 0 acc = acc `seq` True
  rep k Nothing = rep' (k - 1) a
  rep k (Just prev) = a == prev && rep' (k - 1) a
  rep' 0 _ = True
  rep' k prev = a == prev && rep' (k - 1) a
in rep k0 Nothing

Puisque rep n'est plus récursif, GHC peut le réduire:

let
  rep' 0 _ = True
  rep' k prev = a == prev && rep' (k - 1) a
in
  case k0 of
    0 -> True
    _ -> rep' (k - 1) a

Comme vous pouvez le constater, cela peut fonctionner sans aucune allocation! De toute évidence, il s’agit d’un exemple idiot, mais il en ira de même dans de nombreux cas plus intéressants. Par exemple, si vous écrivez un module AllSameTest en important la fonction allSame et en définissant

foo :: Int -> Bool
foo n = allSame [0..n]

et compilez-le comme décrit ci-dessus, vous obtiendrez ce qui suit (non nettoyé).

$wfoo :: Int# -> Bool
$wfoo =
  \ (ww_s1bY :: Int#) ->
    case tagToEnum# (># 0 ww_s1bY) of _ {
      False ->
        letrec {
          $sgo_s1db :: Int# -> Int# -> Bool
          $sgo_s1db =
            \ (sc_s1d9 :: Int#) (sc1_s1da :: Int#) ->
              case tagToEnum# (==# sc_s1d9 sc1_s1da) of _ {
                False -> False;
                True ->
                  case tagToEnum# (==# sc_s1d9 ww_s1bY) of _ {
                    False -> $sgo_s1db (+# sc_s1d9 1) sc_s1d9;
                    True -> True
                  }
              }; } in
        case ww_s1bY of _ {
          __DEFAULT -> $sgo_s1db 1 0;
          0 -> True
        };
      True -> True
    }

foo :: Int -> Bool
foo =
  \ (w_s1bV :: Int) ->
    case w_s1bV of _ { I# ww1_s1bY -> $wfoo ww1_s1bY }

Cela peut paraître dégoûtant, mais vous remarquerez qu’il n’existe de constructeurs : nulle part, et que les Ints sont tous sans boîte, de sorte que la fonction peut s’exécuter avec une allocation nulle.

4
dfeuer

Je pense que je pourrais simplement mettre en œuvre find et refaire ceci . Je trouve cependant instructif de voir ses entrailles. (Notez que la solution dépend de la transitive de l'égalité, mais notez également que le problème requiert que l'égalité soit transitive pour être cohérente.)

sameElement x:y:xs = if x /= y then Nothing else sameElement y:xs
sameElement [x] = Just x
allEqual [] = True
allEqual xs = isJust $ sameElement xs

J'aime la façon dont sameElement jette un œil sur les premiers O(1) de la liste, puis retourne un résultat ou se reproduit sur un suffixe de la liste, en particulier la queue. Je n'ai rien d'intelligent à dire à propos de cette structure, je l'aime juste :-)

Je pense que je fais les mêmes comparaisons que this . Si au lieu de cela j'avais récursif avec sameElement x:xs, je comparerais l'en-tête de la liste d'entrées à chaque élément comme dans la solution 0.

Tangent: on peut, si on le souhaite, signaler les deux éléments non concordants en remplaçant Nothing par Left (x, y) et Just x par Right x et isJust par either (const False) (const True).

0
Jonas Kölker

Bien que peu efficace (il parcourra toute la liste même si les deux premiers éléments ne correspondent pas), voici une solution effrontée:

import Data.List (group)

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

Juste pour le fun.

0

Cette implémentation est supérieure.

allSame [ ] = True
allSame (h:t) = aux h t

aux x1 [ ]                 = True
aux x1 (x2:xs) | x1==x2    = aux x2 xs 
               | otherwise = False

Etant donné la transitivité de l'opérateur (==), en supposant que l'instance de Eq soit bien implémentée, si vous souhaitez assurer l'égalité d'une chaîne d'expressions, par exemple a = b = c = d, il vous suffira de vous assurer que a = b, b = c, c = d et que d = a, au lieu des techniques fournies ci-dessus, par exemple a = b, a = c, a = d, b = c, b = d, c = d. 

La solution que j'ai proposée augmente de manière linéaire avec le nombre d'éléments que vous souhaitez tester. Cette dernière est quadratique, même si vous introduisez des facteurs constants dans l'espoir d'améliorer son efficacité.

C'est également supérieur à la solution utilisant group puisque vous n'avez pas à utiliser la longueur à la fin. 

Vous pouvez aussi l’écrire joliment de manière ponctuelle, mais je ne vous ennuierai pas avec des détails aussi triviaux.

0
Gonçalo Faria