web-dev-qa-db-fra.com

Compter le nombre d'éléments dans une liste qui satisfont le prédicat donné

La bibliothèque standard Haskell a-t-elle une fonction qui, étant donné une liste et un prédicat, renvoie le nombre d'éléments satisfaisant ce prédicat? Quelque chose comme avec le type (a -> Bool) -> [a] -> Int. Ma recherche sur Google n'a retourné rien d'intéressant. J'utilise actuellement length . filter pred, que je ne trouve pas être une solution particulièrement élégante. Mon cas d'utilisation semble être assez courant pour avoir une meilleure solution de bibliothèque que cela. Est-ce le cas ou ma prémonition est-elle fausse?

42
missingfaktor

Le length . filter p l'implémentation n'est pas aussi mauvaise que vous le suggérez. En particulier, il n'a que des frais généraux constants en mémoire et en vitesse, alors oui.

Pour les choses qui utilisent la fusion de flux, comme le package vector, length . filter p sera en fait optimisé pour éviter de créer un vecteur intermédiaire. Les listes, cependant, utilisent ce qu'on appelle foldr/build fusion pour le moment, ce qui n'est pas assez intelligent pour optimiser length . filter p sans créer de gros morceaux linéaires risquant de déborder.

Pour plus de détails sur la fusion de flux, voir cet article . Si je comprends bien, la raison pour laquelle la fusion de flux n'est pas actuellement utilisée dans les principales bibliothèques Haskell est que (comme décrit dans l'article) environ 5% des programmes ont des performances dramatiques pires lorsqu'il est implémenté au-dessus des bibliothèques basées sur les flux, tandis que foldr/build les optimisations ne peuvent jamais (AFAIK) aggraver activement les performances.

43
Louis Wasserman

Non, il n'y a pas de fonction prédéfinie qui le fasse, mais je dirais que length . filter pred est, en fait, une implémentation élégante; c'est aussi proche que possible d'exprimer ce que vous voulez dire sans invoquer directement le concept, ce que vous ne pouvez pas faire si vous le définissez.

Les seules alternatives seraient une fonction récursive ou un pli, ce que l'OMI serait moins élégant, mais si vous voulez vraiment:

foo :: (a -> Bool) -> [a] -> Int
foo p = foldl' (\n x -> if p x then n+1 else n) 0

Il s'agit simplement d'inclure length dans la définition. Quant à la dénomination, je suggérerais count (ou peut-être countBy, puisque count est un nom de variable raisonnable).

7
ehird

Haskell est un langage de haut niveau. Plutôt que de fournir une fonction pour chaque combinaison possible de circonstances que vous pourriez rencontrer, il vous fournit un petit ensemble de fonctions qui couvrent toutes les bases, puis vous les collez ensemble selon les besoins pour résoudre le problème actuel.

En termes de simplicité et de concision, c'est aussi élégant que possible. Donc oui, length . filter pred est absolument la solution standard. Comme autre exemple, considérons elem, qui (comme vous le savez peut-être) vous indique si un élément donné est présent dans une liste. L'implémentation de référence standard pour cela est en fait

 elem :: Eq x => x -> [x] -> Bool 
 elem x = foldr (||) Faux. carte (x ==) 

Dans les mots d'ordre, comparez chaque élément de la liste à l'élément cible, créant une nouvelle liste de Bools. Pliez ensuite la fonction OR logique sur cette nouvelle liste.

Si cela semble inefficace, essayez de ne pas vous en soucier. En particulier,

  1. Le compilateur peut souvent optimiser les structures de données temporaires créées par un code comme celui-ci. (N'oubliez pas, c'est la manière standard d'écrire du code dans Haskell, donc le compilateur est réglé pour y faire face.)

  2. Même s'il ne peut pas être optimisé, la paresse rend souvent ce code assez efficace de toute façon.

(Dans cet exemple spécifique, la fonction OR terminera la boucle dès qu'une correspondance est vue - tout comme ce qui se passerait si vous la codiez vous-même à la main.)

En règle générale, écrivez du code en collant des fonctions préexistantes. Ne modifiez cela que si les performances ne sont pas suffisantes.

6
MathematicalOrchid

Ceci est ma solution amateur à un problème similaire. Compter le nombre d'entiers négatifs dans une liste l

nOfNeg l = length(filter (<0) l)
main = print(nOfNeg [0,-1,-2,1,2,3,4] ) --2
1
JayJay

Non, il n'y en a pas!

En 2020, il n'y a en effet aucun idiome de ce type dans la bibliothèque standard de Haskell! On pourrait (et devrait) cependant insérer un idiome howMany (ressemblant au bon vieux any)

howMany p xs = sum [ 1 | x <- xs, p x ]
-- howMany=(length.).filter

main = print $ howMany (/=0) [0..9]

Essayez howMany = (length.). Filter

0
Roman Czyborra