R - 如何从表达式中提取对象名称
R - How to extract object names from expression
给定一个 rlang 表达式:
expr1 <- rlang::expr({
d <- a + b
})
如何检索表达式中引用的对象的名称?
> extractObjects(expr1)
[1] "d" "a" "b"
更好的是,如何检索对象名称并按“必需”(输入)和“创建”(输出)对它们进行分类?
> extractObjects(expr1)
$created
[1] "d"
$required
[1] "a" "b"
这很有趣。我认为从概念上讲,在所有可能的表达式中可能不清楚输入和输出到底是什么。如果您查看所谓的抽象语法树 (AST),您可以使用 lobstr::ast()
对其进行可视化,它看起来像这样。
因此,在总是有 LHS <- operations on RHS variables
的简单情况下,如果您遍历 AST,您将始终在 <-
运算符之后立即获得 LST。如果您分配 z <- rlang::expr(d <- a+b)
,那么 z
就像一个列表,例如您可以执行以下操作:
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"
由 reprex 包 (v0.3.0) 创建于 2020-09-03
如您所见,这些树很快就会变得复杂和嵌套。因此,在您的示例中的简单情况下,假设变量使用字母数字表示,我们可以识别“对象”(如您所说)是什么以及运算符是什么(与 [[:alnum:]]
正则表达式)。如您所见,类型不能用于区分对象和运算符,因为它始终是 symbol
(顺便说一句,下面的 z
是 language
和 z[[3]]
这就是为什么我们可以以 z[[i]]
是否为 symbol
为条件,如果不是,则深入挖掘)。然后,您可以(自担风险)尝试将 <-
之后立即出现的对象分类为“输出”,其余为“输入”,但我对此没有太大信心,尤其是对于更多复杂的表达式。
简而言之,这都是非常投机的。
另一个解决方案是:
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
然后遍历AST找到assignment(s):
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"
如果需要,您可以 。
基本函数 all.vars
执行此操作:
〉all.vars(expr1)
[1] "d" "a" "b"
或者,您可以使用 all.names
获取表达式中的 所有 个名称,而不仅仅是那些未用作调用或运算符的名称:
〉all.names(expr1)
[1] "{" "<-" "d" "+" "a" "b"
不要被误导:这个结果是正确的! 所有这些都出现在表达式中,而不仅仅是a
、b
和 d
.
但它可能不是你想要的。
事实上,我假设您想要的对应于抽象语法树 (AST) 中的叶标记 - 换句话说,除了函数调用(和运算符,它们也是函数调用)之外的所有内容。
表达式的语法树如下所示:1
{
|
<-
/\
d +
/ \
a b
获取此信息意味着遍历 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"
感谢 AST 表示,我们还可以找到输入和输出:
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"
请注意,此函数不能很好地处理复杂的赋值(即像 d[x] <- a + b
或 f(d) <- a + b
这样的东西。
1 lobstr::ast
以不同方式显示语法树,即
<sub>█─`{`
└─█─`<-`
├─d
└─█─`+`
├─a
└─b</sub>
...但是上面的表示在R之外比较常规,我觉得比较直观。
给定一个 rlang 表达式:
expr1 <- rlang::expr({
d <- a + b
})
如何检索表达式中引用的对象的名称?
> extractObjects(expr1)
[1] "d" "a" "b"
更好的是,如何检索对象名称并按“必需”(输入)和“创建”(输出)对它们进行分类?
> extractObjects(expr1)
$created
[1] "d"
$required
[1] "a" "b"
这很有趣。我认为从概念上讲,在所有可能的表达式中可能不清楚输入和输出到底是什么。如果您查看所谓的抽象语法树 (AST),您可以使用 lobstr::ast()
对其进行可视化,它看起来像这样。
因此,在总是有 LHS <- operations on RHS variables
的简单情况下,如果您遍历 AST,您将始终在 <-
运算符之后立即获得 LST。如果您分配 z <- rlang::expr(d <- a+b)
,那么 z
就像一个列表,例如您可以执行以下操作:
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"
由 reprex 包 (v0.3.0) 创建于 2020-09-03
如您所见,这些树很快就会变得复杂和嵌套。因此,在您的示例中的简单情况下,假设变量使用字母数字表示,我们可以识别“对象”(如您所说)是什么以及运算符是什么(与 [[:alnum:]]
正则表达式)。如您所见,类型不能用于区分对象和运算符,因为它始终是 symbol
(顺便说一句,下面的 z
是 language
和 z[[3]]
这就是为什么我们可以以 z[[i]]
是否为 symbol
为条件,如果不是,则深入挖掘)。然后,您可以(自担风险)尝试将 <-
之后立即出现的对象分类为“输出”,其余为“输入”,但我对此没有太大信心,尤其是对于更多复杂的表达式。
简而言之,这都是非常投机的。
另一个解决方案是
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
然后遍历AST找到assignment(s):
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"
如果需要,您可以
基本函数 all.vars
执行此操作:
〉all.vars(expr1)
[1] "d" "a" "b"
或者,您可以使用 all.names
获取表达式中的 所有 个名称,而不仅仅是那些未用作调用或运算符的名称:
〉all.names(expr1)
[1] "{" "<-" "d" "+" "a" "b"
不要被误导:这个结果是正确的! 所有这些都出现在表达式中,而不仅仅是a
、b
和 d
.
但它可能不是你想要的。
事实上,我假设您想要的对应于抽象语法树 (AST) 中的叶标记 - 换句话说,除了函数调用(和运算符,它们也是函数调用)之外的所有内容。
表达式的语法树如下所示:1
{
|
<-
/\
d +
/ \
a b
获取此信息意味着遍历 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"
感谢 AST 表示,我们还可以找到输入和输出:
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"
请注意,此函数不能很好地处理复杂的赋值(即像 d[x] <- a + b
或 f(d) <- a + b
这样的东西。
1 lobstr::ast
以不同方式显示语法树,即
<sub>█─`{`
└─█─`<-`
├─d
└─█─`+`
├─a
└─b</sub>
...但是上面的表示在R之外比较常规,我觉得比较直观。