我正在翻译一种旧语言(FOCAL)的代码,该语言使用“()”作为函数参数和向量索引。目标语言(R)也使用“()”作为函数,但使用“[]”作为索引。FOCAL
变量名限制为两个字符,至少在开始时,我假设所有函数名都有 3 个或更多字符(所有内部函数都是如此)。例如,字符串
" ab(c(kk)) ab(c(5)) sin(x) qr(g) exp(cos(qr)) exp(z) exp(cos(qr(3))) cos(y(3)) sin(3)"
应转换为
" ab[c[kk] ] ab[c[5] ] sin(x) qr[g] exp(cos(qr)) exp(z) exp(cos(qr[3] )) cos(y[3] ) sin(3)"
作为第一步,我首先通过这个正则表达式(R 代码)运行输入来查找数字索引,
str1 <- gsub('([^a-z][a-z]{1,2})[(]([0-9]{1,})[)]' ,'\\1\\[\\2\\] ' , thestr)
接下来我寻找变量名称的索引
str2 <- gsub('([^a-z][a-z]{1,2})[(]([^)]{1,})[)]' ,'\\1\\[\\2\\] ' , str1)
仅当我对第二个正则表达式进行递归时,它才会成功。我希望有一种更简洁的方法,最好是一种不需要重复操作的方法。
3
最佳答案
1
下面的前两个替代方案将输入拆分为有效的 R 代码的子字符串,然后使用 R 的 tokenzier 或内部表达式树。第三和第四个替代方案直接使用正则表达式或处理字符串gsubfn
。(3)可能是最快的。
1) getParseData不清楚一般情况会是什么,但如果我们修复不平衡的括号,cos(y(3)))
那么这将适用于显示的输入。它会在空格处中断输入,然后应用于fix_parens
每个组件。它使用标记输入getParseData
,然后跟踪括号类型,parens
替换跟在一个或两个字符名称及其匹配项后面的括号。utils 包随 R 一起提供,因此无需安装。
library(utils)
fix_parens <- function(x) {
txt <- getParseData(parse(text = x))$text
txt <- txt[txt != ""]
out <- txt
paren <- c()
for(i in seq_along(txt)[-1]) {
if (nchar(txt[i-1]) %in% 1:2 && txt[i] == "(") {
paren <- c(paren, "]")
out[i] <- "["
} else if (txt[i] == "(") {
paren <- c(paren, ")")
} else if (txt[i] == ")") {
out[i] <- tail(paren, 1)
paren <- head(paren, -1)
}
}
paste0(out, collapse = "")
}
# same as revised version in question
inp <- " ab(c(kk)) ab(c(5)) sin(x) qr(g) exp(cos(qr)) exp(z) exp(cos(qr(3))) cos(y(3)) sin(3)"
s <- strsplit(inp, " +")[[1]]
s <- s[s != ""]
paste(unname(sapply(s, fix_parens)), collapse = " ")
## [1] "ab[c[kk]] ab[c[5]] sin(x) qr[g] exp(cos(qr)) exp(z) exp(cos(qr[3])) cos(y[3]) sin(3)"
2)R 表达式另一种方法是使用带有递归函数的 R 表达式。 s
来自(1),
fix_parens2 <- function(x) {
if (is.character(x)) x <- parse(text = x)[[1]]
if (is.symbol(x) || is.numeric(x)) return(x)
n <- nchar(as.character(x[[1]]))
if (n %in% 1:2) {
as.call(c(as.name("["), x[[1]], fix_parens2(x[[2]])))
} else as.call(c(x[[1]], lapply(x[-1], fix_parens2)))
}
paste(unname(sapply(s, fix_parens2)), collapse = " ")
## [1] "ab[c[kk]] ab[c[5]] sin(x) qr[g] exp(cos(qr)) exp(z) exp(cos(qr[3])) cos(y[3]) sin(3)"
3) 正则表达式 这使用递归正则表达式来匹配 1 或 2 个单词名称、括号和内容,并匹配括号根据需要替换最外层实例重复。
fix_parens3 <- function(x) {
pat <- "(\\b\\w{1,2})\\(((?:[^()]+|(?R))*)\\)"
prev <- x
repeat {
x <- gsub(pat, "\\1[\\2]", x, perl = TRUE)
if (x == prev) break
prev <- x
}
x
}
fix_parens3(inp)
## [1] " ab[c[kk]] ab[c[5]] sin(x) qr[g] exp(cos(qr)) exp(z) exp(cos(qr[3])) cos(y[3]) sin(3)"
4) gsubfn gsubfn
与之类似gsub
,只是替换字符串可以是替换 proto 对象,它将匹配输入到方法中fun
,并用 的输出替换匹配fun
。 pre
在字符串的开头运行,以初始化fun
调用中使用的任何变量fun
。逻辑非常接近 (1),但没有明确的循环。
library(gsubfn)
p <- proto(pre = function(this) this$paren <- c(),
fun = function(this, x) {
if (nchar(x) %in% 2:3) {
this$paren <- c(this$paren, "]")
sub("(", "[", x, fixed = TRUE)
} else if (nchar(x) > 3) {
this$paren <- c(this$paren, ")")
x
} else {
ret <- tail(this$paren, 1)
this$paren <- head(this$paren, -1)
ret
}
})
gsubfn("(\\w+\\(|\\))", p, inp)
## [1] " ab[c[kk]] ab[c[5]] sin(x) qr[g] exp(cos(qr)) exp(z) exp(cos(qr[3])) cos(y[3]) sin(3)"
3
-
这些看起来不错。我修复了多余的括号(提到这一点是为了让未来的读者不会感到困惑)
– -
添加了替代方案(3)和(4)。
– -
哇哦。我想我真的刺激了你的好奇心神经元!:-)
–
|
–
--no-run
选项可以充当解析器。我不确定该选项的输出是什么,但我同意尝试使用正则表达式实现自己的解析器不会有太大帮助,因此,根据任务的复杂性,这可能是一个不错的起点。–
–
|