Les fonctions : paramètres en nombre variable (1ère partie)

Jusqu’à présent, vous avez écrit des fonctions avec un nombre explicite/défini de paramètres à passer, éventuellement avec certains facultatifs car possédant une valeur par défaut.

Ainsi, on nous avons déjà vu précédemment dans l’article Les fonctions (premier contact) la fonction multiplie(x,y) tel que multiplie(2,3) donne 6. Je vous remets la définition :

multiplie <- function(a, b){
          return(a * b)
}

mais on aimerait que multiplie(1,2,3,4) donne 24. Dans l’état cela ne fonctionne pas :

> multiplie(1,2,3,4)
Error in multiplie(1, 2, 3, 4) : unused arguments (3, 4)

Bien sûr nous pourrions tricher pour rendre multiplie compatible avec 4 arguments :

multiplie <- function(a, b, c = 1, d = 1){
          return(a * b * c * d)
}

Mais cela serait de la bidouille et à nouveau limité à un nombre fixé d’arguments (4 dans le cas présent) :

> multiplie(1,2,3)
[1] 6
> multiplie(1,2,3,4)
[1] 24
> multiplie(1,2,3,4,5)
Error in multiplie(1, 2, 3, 4, 5) : unused argument (5)

Sachez qu’en R, il est possible d’écrire une fonction prenant un nombre variable de paramètres.

Pour cela, on peut utiliser ... dans la définition. Ces paramètres seront alors accessibles soit directement via ..1, ..2, ..3, ..n etc. soit via des fonctions spécifiques : ...names() donnant la liste des noms des paramètres, ...elt(n) retournant la nième valeur des paramètres et ...length() donnant le nombre de paramètres dans ....

Ainsi :

multiplie <- function(...) {..1 * ..2 * ..3}

permet de multiplier les 3 premiers paramètres passés à la fonction. Mais le problème reste le même si on en passe moins de 3 ou plus de 3 (pire car cela ne génère pas d’erreur et le résultat est faux :

> multiplie(1,2)
Error in fonction1(1, 2) : the ... list contains fewer than 3 elements
> multiplie(1,2,z=3)
[1] 6
> multiplie(1,2,3,4)
[1] 6                  # OUPS c'est faux !

(Le nom de paramètre « z » n’est pas utilisé dans le cas présent mais la valeur, oui).

Essayons désormais de généraliser en écrivant une fonction qui multiplie tous les paramètres entre eux. Pour cela nous allons utiliser l’autre façon d’écrire : ...

multiplie <- function(...){r=1;for(x in list(...)) {r <- r*x};r}

Notre fonction marche pour tout nombre de paramètre supérieur ou égal à 1 mais 0 donne… 1… il faudrait donc rajouter un test initial. Je vous laisse chercher, si besoin vous trouverez ci-dessous 2 solutions, à base de switch() : (les print(...length()) ne sont là qu’à titre d’illustration)

multiplie <- function(...){
   print(...length())
   switch(as.character(...length()),
          "0" = NULL,
          "1" = NULL,
          {r=1;for(x in list(...)) {r <- r*x};return(r)}
    )
}

ou de if :

multiplie <- function(...){
   print(...length())
   if(...length() %in% c(0,1)) 
      NULL
   else
      {r=1;for(x in list(...)) {r <- r*x};r}
}
> multiplie(1)
[1] 1
NULL
> multiplie()
[1] 0
NULL
> multiplie(1)
[1] 1
NULL
> multiplie(1,2)
[1] 2
[1] 2
> multiplie(1,2,3)
[1] 3
[1] 6
> multiplie(1,2,3,4,5,6,7,8,9,10)
[1] 10
[1] 3628800

Bonus : En fait, nous réinventions la roue

Les exemples donnés plus haut, sont fait pour vous faire comprendre le fonctionnement de R, pas pour l’efficacité ni même la qualité du code.
En effet, il existe déjà une fonction simplifiant drastiquement l’écriture : Reduce qui applique une fonction binaire (au sens de nécessitant deux variables) – ici la multiplication – à une liste de valeurs. On peut donc écrire au plus simple :

 multiplie <- function(...) Reduce("*",list(...))

Ce qui nous donne à l’exécution :

> multiplie()
NULL
> multiplie(1)
[1] 1
> multiplie(1,2)
[1] 2
> multiplie(1,2,3)
[1] 6
> multiplie(1,2,3,4)
[1] 24

Youpi, on est arrivé à ce qu’on voulait pour cette fois-ci en une ligne !

Attention cependant, les éléments passés à la fonction doivent être compatibles avec la fonction sous-jacente (là tous les paramètres doivent être numériques sous peine d’erreur à l’exécution)

A une prochaine fois !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *