Comment accéder à une liste par index dans Haskell, analogue à ce code C?
int a[] = { 34, 45, 56 };
return a[1];
Regardez ici , opérateur !!
.
C'est à dire. [1,2,3]!!1
vous donne 2
, puisque les listes sont indexées par 0.
Je ne dis pas qu'il y a quelque chose qui cloche dans votre question ou la réponse donnée, mais peut-être aimeriez-vous connaître le merveilleux outil qu'est Hoogle pour vous faire gagner du temps à l'avenir: avec Hoogle, vous pouvez rechercher des fonctions de bibliothèque standard correspondant à une signature donnée. Donc, ne sachant rien à propos de !!
, dans votre cas, vous pouvez rechercher "quelque chose qui prend un Int
et une liste de whatevers et retourne un seul", à savoir
Int -> [a] -> a
Lo et voici , avec !!
comme premier résultat (bien que la signature de type ait en fait les deux arguments inversés par rapport à ce que nous avons recherché). Neat, hein?
En outre, si votre code repose sur l'indexation (au lieu de consommer au début de la liste), les listes peuvent en fait ne pas être la structure de données appropriée. Pour un accès basé sur index O(1)), il existe des alternatives plus efficaces, telles que tableaux ou vecteurs .
Une alternative à l'utilisation de (!!)
est d'utiliser le paquet lens et sa fonction element
ainsi que ses opérateurs associés. lens fournit une interface uniforme pour accéder à une grande variété de structures et de structures imbriquées au-dessus et au-delà des listes. Ci-dessous, je me concentrerai sur des exemples et sur les signatures de types et la théorie sous-jacente au paquet lens . Si vous voulez en savoir plus sur la théorie, un bon point de départ est le fichier lisez-moi au github repo .
En ligne de commande:
$ cabal install lens
$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
> import Control.Lens
Pour accéder à une liste avec l'opérateur infixe
> [1,2,3,4,5] ^? element 2 -- 0 based indexing
Just 3
Contrairement au (!!)
ceci ne lèvera pas une exception lorsqu’on accédera à un élément en dehors des limites et retournera Nothing
à la place. Il est souvent recommandé d'éviter des fonctions partielles comme (!!)
ou head
car ils ont plus de cas et sont plus susceptibles de provoquer une erreur d'exécution. Vous pouvez en savoir un peu plus sur les raisons pour lesquelles il faut éviter les fonctions partielles sur cette page wiki .
> [1,2,3] !! 9
*** Exception: Prelude.(!!): index too large
> [1,2,3] ^? element 9
Nothing
Vous pouvez forcer la technique de la lentille à être une fonction partielle et renvoyer une exception en dehors des limites en utilisant le (^?!)
opérateur à la place du (^?)
opérateur.
> [1,2,3] ^?! element 1
2
> [1,2,3] ^?! element 9
*** Exception: (^?!): empty Fold
Ce n'est pas seulement limité aux listes cependant. Par exemple, la même technique fonctionne sur arbres à partir du package standard conteneurs .
> import Data.Tree
> :{
let
tree = Node 1 [
Node 2 [Node 4[], Node 5 []]
, Node 3 [Node 6 [], Node 7 []]
]
:}
> putStrLn . drawTree . fmap show $tree
1
|
+- 2
| |
| +- 4
| |
| `- 5
|
`- 3
|
+- 6
|
`- 7
Nous pouvons maintenant accéder aux éléments de l'arbre en profondeur-premier ordre:
> tree ^? element 0
Just 1
> tree ^? element 1
Just 2
> tree ^? element 2
Just 4
> tree ^? element 3
Just 5
> tree ^? element 4
Just 3
> tree ^? element 5
Just 6
> tree ^? element 6
Just 7
Nous pouvons également accéder à séquences à partir du paquet conteneurs :
> import qualified Data.Sequence as Seq
> Seq.fromList [1,2,3,4] ^? element 3
Just 4
Nous pouvons accéder aux tableaux indexés standard int à partir du package vector , du texte à partir du package standard text , des bytestrings à la norme bytestring , et de nombreux autres autres structures de données standard. Cette méthode d'accès standard peut être étendue à vos structures de données personnelles en les transformant en une instance de la classe Taversable , voir une liste plus longue d'exemple Traversables dans la documentation de Lens. .
Creuser dans les structures imbriquées est simple avec la lentille hackage . Par exemple, accéder à un élément dans une liste de listes:
> [[1,2,3],[4,5,6]] ^? element 0 . element 1
Just 2
> [[1,2,3],[4,5,6]] ^? element 1 . element 2
Just 6
Cette composition fonctionne même lorsque les structures de données imbriquées sont de types différents. Donc par exemple si j'avais une liste d'arbres:
> :{
let
tree = Node 1 [
Node 2 []
, Node 3 []
]
:}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
|
`- 3
> :{
let
listOfTrees = [ tree
, fmap (*2) tree -- All tree elements times 2
, fmap (*3) tree -- All tree elements times 3
]
:}
> listOfTrees ^? element 1 . element 0
Just 2
> listOfTrees ^? element 1 . element 1
Just 4
Vous pouvez imbriquer profondément de manière arbitraire avec des types arbitraires tant qu'ils répondent à l'exigence Traversable
. Donc, accéder à une liste d'arbres de séquences de texte n'est pas une sueur.
Une opération courante dans de nombreuses langues consiste à affecter une position indexée dans un tableau. Dans python vous pourriez:
>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
Le paquet lens donne cette fonctionnalité avec le (.~)
opérateur. Bien que contrairement à python, la liste d'origine ne soit pas mutée, une nouvelle liste est renvoyée.
> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]
element 3 .~ 9
n'est qu'une fonction et le (&)
opérateur, faisant partie du paquet lens , n’est qu’une application de fonction inverse. Ici, c'est avec l'application de fonction la plus commune.
> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
L'affectation fonctionne à nouveau parfaitement avec une imbrication arbitraire de Traversable
s.
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
La réponse directe était déjà donnée: utilisez !!
.
Cependant, les débutants ont souvent tendance à abuser de cet opérateur, ce qui est coûteux en Haskell (parce que vous travaillez sur des listes liées simples, pas sur des tableaux). Il existe plusieurs techniques utiles pour éviter cela, la plus simple consiste à utiliser Zip. Si vous écrivez Zip ["foo","bar","baz"] [0..]
, Vous obtenez une nouvelle liste avec les index "attachés" à chaque élément d'une paire: [("foo",0),("bar",1),("baz",2)]
, qui correspond souvent exactement à ce dont vous avez besoin.
Le type de données de liste standard de Haskell forall t. [t]
Dans son implémentation ressemble beaucoup à une liste chaînée C chaînée et partage ses propriétés essentielles. Les listes chaînées sont très différentes des tableaux. Plus particulièrement, l'accès par index est une opération O(n) linear-, au lieu d'une opération O(1) à temps constant.
Si vous avez besoin d'un accès aléatoire fréquent, considérez le standard Data.Array
.
!!
Est une fonction partiellement définie non sûre, provoquant un crash pour les index hors limites. Sachez que la bibliothèque standard contient certaines de ces fonctions partielles (head
, last
, etc.). Pour des raisons de sécurité, utilisez un type d’option Maybe
ou le module Safe
.
Exemple de fonction d’indexation totale (pour les indices ≥ 0) raisonnablement efficace et robuste:
data Maybe a = Nothing | Just a
lookup :: Int -> [a] -> Maybe a
lookup _ [] = Nothing
lookup 0 (x : _) = Just x
lookup i (_ : xs) = lookup (i - 1) xs
Travailler avec des listes chaînées, les ordinaux sont souvent pratiques:
nth :: Int -> [a] -> Maybe a
nth _ [] = Nothing
nth 1 (x : _) = Just x
nth n (_ : xs) = nth (n - 1) xs
Vous pouvez utiliser !!
, mais si vous voulez le faire de manière récursive, voici une façon de le faire:
dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs) | y <= 0 = x
| otherwise = dataAt (y-1) xs