Comment extraire les noms d'objet de l'expression
Étant donné une expression rlang:
expr1 <- rlang::expr({
d <- a + b
})
Comment récupérer les noms des objets référencés dans l'expression?
> extractObjects(expr1)
[1] "d" "a" "b"
Mieux encore, comment récupérer les noms des objets et les classer par «requis» (entrée) et «créé» (sortie)?
> extractObjects(expr1)
$created [1] "d" $required
[1] "a" "b"
Réponses
La fonction de base all.varsfait ceci:
〉all.vars(expr1)
[1] "d" "a" "b"
Vous pouvez également utiliser all.namespour obtenir tous les noms de l'expression plutôt que ceux qui ne sont pas utilisés comme appels ou opérateurs:
〉all.names(expr1)
[1] "{" "<-" "d" "+" "a" "b"
Ne vous y trompez pas: ce résultat est correct! Tous ces éléments apparaissent dans l'expression, pas seulement a, bet d.
Mais ce n'est peut-être pas ce que vous voulez.
En fait, je suppose que ce que vous voulez correspond aux jetons de feuille dans l'arbre de syntaxe abstraite (AST) - en d'autres termes, tout sauf les appels de fonction (et les opérateurs, qui sont également des appels de fonction).
L'arbre de syntaxe de votre expression se présente comme suit: 1
{
|
<-
/\
d +
/ \
a b
Obtenir ces informations signifie marcher sur l'AST:
leaf_nodes = function (expr) {
if(is.call(expr)) {
unlist(lapply(as.list(expr)[-1L], leaf_nodes))
} else {
as.character(expr)
}
}
〉leaf_nodes(expr1)
[1] "d" "a" "b"
Grâce à la représentation AST, nous pouvons également trouver des entrées et des sorties:
is_assignment = function (expr) {
is.call(expr) && as.character(expr[[1L]]) %in% c('=', '<-', '<<-', 'assign')
}
vars_in_assign = function (expr) {
if (is.call(expr) && identical(expr[[1L]], quote(`{`))) {
vars_in_assign(expr[[2L]])
} else if (is_assignment(expr)) {
list(created = all.vars(expr[[2L]]), required = all.vars(expr[[3L]]))
} else {
stop('Expression is not an assignment')
}
}
〉vars_in_assign(expr1)
$created [1] "d" $required
[1] "a" "b"
Notez que cette fonction ne gère pas les affectations complexes (c'est-à-dire des trucs similaires d[x] <- a + bou f(d) <- a + btrès bien.
1 lobstr::ast montre l'arbre de syntaxe différemment, à savoir comme
█─`{` └─█─`<-` ├─d └─█─`+` ├─a └─b
… Mais la représentation ci-dessus est plus conventionnelle en dehors de R, et je la trouve plus intuitive.
Une autre solution consiste à extraire explicitement l'arbre symbolique abstrait :
getAST <- function(ee) purrr::map_if(as.list(ee), is.call, getAST)
str(getAST(expr1))
# List of 2
# $ : symbol { # $ :List of 3
# ..$ : symbol <- # ..$ : symbol d
# ..$ :List of 3 # .. ..$ : symbol +
# .. ..$ : symbol a # .. ..$ : symbol b
Parcourez ensuite l'AST pour trouver la ou les affectations:
extractObjects <- function(ast)
{
## Ensure that there is at least one node
if( length(ast) == 0 ) stop("Provide an AST")
## If we are working with the assigment
if( identical(ast[[1]], as.name("<-")) ) {
## Separate the LHS and RHS
list(created = as.character(ast[[2]]),
required = sapply(unlist(ast[[3]]), as.character))
} else {
## Otherwise recurse to find all assignments
rc <- purrr::map(ast[-1], extractObjects)
## If there was only one assignment, simplify reporting
if( length(rc) == 1 ) purrr::flatten(rc)
else rc
}
}
extractObjects( getAST(expr1) )
# $created # [1] "d" # # $required
# [1] "+" "a" "b"
Vous pouvez ensuite filtrer les opérateurs mathématiques , si nécessaire.
Celui-ci est intéressant. Je pense que sur le plan conceptuel, il se peut que TOUTES les expressions possibles ne comprennent pas exactement ce que sont exactement les entrées et les sorties. Si vous regardez l'arborescence de syntaxe abstraite (AST), que vous pouvez visualiser avec lobstr::ast(), cela ressemble à ceci.
Ainsi, dans les cas simples où vous l'avez toujours fait LHS <- operations on RHS variables, si vous itérez sur l'AST, vous obtiendrez toujours le LST juste après l' <-opérateur. Si vous attribuez z <- rlang::expr(d <- a+b), alors zse comporte comme une liste et vous pouvez par exemple faire ce qui suit:
z <- rlang::expr(d <- a+b)
for (i in 1:length(z)) {
if (is.symbol(z[[i]])) {
print(paste("Element", i, "of z:", z[[i]], "is of type", typeof(z[[i]])))
if (grepl("[[:alnum:]]", z[[i]])) {print(paste("Seems like", z[[i]], "is a variable"))}
} else {
for (j in 1:length(z[[i]])){
print(paste("Element", j, paste0("of z[[",i,"]]:"), z[[i]][[j]], "is of type", typeof(z[[i]][[j]])))
if (grepl("[[:alnum:]]", z[[i]][[j]])) {print(paste("Seems like", z[[i]][[j]], "is a variable"))}
}
}
}
#> [1] "Element 1 of z: <- is of type symbol"
#> [1] "Element 2 of z: d is of type symbol"
#> [1] "Seems like d is a variable"
#> [1] "Element 1 of z[[3]]: + is of type symbol"
#> [1] "Element 2 of z[[3]]: a is of type symbol"
#> [1] "Seems like a is a variable"
#> [1] "Element 3 of z[[3]]: b is of type symbol"
#> [1] "Seems like b is a variable"
Créé le 2020-09-03 par le package reprex (v0.3.0)
Comme vous pouvez le voir, ces arbres peuvent rapidement devenir compliqués et imbriqués. Donc, dans un cas simple comme dans votre exemple, en supposant que les variables utilisent des représentations alphanumériques, nous pouvons en quelque sorte identifier ce que sont les "objets" (comme vous les appelez) et quels sont les opérateurs (qui ne correspondent pas à l' [[:alnum:]]expression régulière). Comme vous pouvez le voir, le type ne peut pas être utilisé pour distinguer les objets et les opérateurs car il est toujours symbol(btw zci-dessous est un languagetel z[[3]]quel, c'est pourquoi nous pouvons conditionner si z[[i]]est un symbolou non et sinon, creuser un niveau plus profond). Vous pourriez alors (à vos risques et périls) essayer de classer que les objets qui apparaissent immédiatement après un <-sont des "sorties" et le reste des "entrées" mais je n'ai pas trop confiance en cela, surtout pour les expressions plus complexes.
En bref, tout cela est très spéculatif.