用数学表达式中的C's pow语法替换^(幂)符号

Kal*_*xey 15 r

我有一个数学表达式,例如:

((2-x+3)^2+(x-5+7)^10)^0.5
Run Code Online (Sandbox Code Playgroud)

我需要将^符号替换pow为C语言的功能.我认为正则表达式是我需要的,但我不知道像专业人士那样的正则表达式.所以我最终得到了这个正则表达式:

(\([^()]*)*(\s*\([^()]*\)\s*)+([^()]*\))*
Run Code Online (Sandbox Code Playgroud)

我不知道如何改善这一点.你能建议我解决这个问题吗?

预期产量:

pow(pow(2-x+3,2)+pow(x-5+7,10),0.5)
Run Code Online (Sandbox Code Playgroud)

Bro*_*ieG 17

关于R最神奇的事情之一就是你可以用R轻松地操作R表达式.这里,我们递归遍历你的表达式并替换所有的^with pow:

f <- function(x) {
  if(is.call(x)) {
    if(identical(x[[1L]], as.name("^"))) x[[1L]] <- as.name("pow")
    if(length(x) > 1L) x[2L:length(x)] <- lapply(x[2L:length(x)], f)
  }
  x
}
f(quote(((2-x+3)^2+(x-5+7)^10)^0.5))

## pow((pow((2 - x + 3), 2) + pow((x - 5 + 7), 10)), 0.5)
Run Code Online (Sandbox Code Playgroud)

这应该比正则表达式更强大,因为您依赖于R语言的自然解释而不是可能或可能不全面的文本模式.


详细信息:R中的调用存储在列表类似结构中,其中函数/运算符位于列表的开头,以及以下元素中的参数.例如,考虑:

exp <- quote(x ^ 2)
exp
## x^2
is.call(exp)
## [1] TRUE
Run Code Online (Sandbox Code Playgroud)

我们可以用以下方法检查调用的底层结构as.list:

str(as.list(exp))
## List of 3
##  $ : symbol ^
##  $ : symbol x
##  $ : num 2
Run Code Online (Sandbox Code Playgroud)

如您所见,第一个元素是函数/运算符,后续元素是函数的参数.

所以,在我们的递归函数中,我们:

  • 检查对象是否是呼叫
    • 如果是:通过查看调用^中的第一个元素来检查它是否是对函数/运算符的调用identical(x[[1L]], as.name("^")
      • 如果是:用第一个元素替换 as.name("pow")
      • 然后,无论这是一个呼叫^还是其他任何事情:
        • 如果调用有其他元素,循环它们并将此函数(即递归)应用于每个元素,将结果替换回原始调用(x[2L:length(x)] <- lapply(x[2L:length(x)], f))
    • 如果否:只返回对象不变

请注意,调用通常包含函数名称作为第一个元素.您可以使用创建这些名称as.name.名称在R中也称为"符号"(因此是输出str).

  • 我猜测正则表达式会更快,但两者都应该足够快,执行速度不应该是决定使用哪个的因素.在我看来,更重要的是在所有情况下结果都是正确的,这在正则表达式中极难保证. (2认同)

Wik*_*żew 13

免责声明:答案是用OP原始正则表达式编写的,当问题听起来是"处理^前面有平衡(嵌套)括号".请不要将此解决方案用于通用数学表达式解析,仅用于教育目的,并且仅当您确实需要在平衡括号上下文中处理某些文本时.

由于PCRE正则表达式可以匹配嵌套的括号,因此可以在R中用while循环中的纯正则表达式来检查^修改后的字符串中是否存在x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE).一旦没有^,就没有别的东西可以替代.

正则表达式是

(\(((?:[^()]++|(?1))*)\))\^(\d*\.?\d+)
Run Code Online (Sandbox Code Playgroud)

请参阅正则表达式演示

细节:

  • (\(((?:[^()]++|(?1))*)\))- 第1组:(...)带有平衡括号的子字符串,捕获外括号内的内容到第2组(((?:[^()]++|(?1))*)带子模式)(解释可以在如何使用正则表达式匹配嵌套括号?),简而言之,\匹配外部(,然后(?:[^()]++|(?1))*匹配零或者除了(和/ )或整个第1组子模式((?1)子程序调用)之后的1 + chars的更多序列然后a ))
  • \^- 一个^插入符号
  • (\d*\.?\d+)-第3组:一个int /浮点数(.5,1.5,345)

替换模式包含文字pow(),\\2并且\\3是对第2组和第3组捕获的子字符串的反向引用.

R代码:

v <- "((2-x+3)^2+(x-5+7)^10)^0.5"
x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE)
while(x) {
    v <- sub("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", "pow(\\2, \\3)", v, perl=TRUE);
    x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE)
}
v
## => [1] "pow(pow(2-x+3, 2)+pow(x-5+7, 10), 0.5)"
Run Code Online (Sandbox Code Playgroud)

为了支持^(x-3) pows,你可以使用

 v <- sub("(\\(((?:[^()]++|(?1))*)\\))\\^(?|()(\\d*\\.?\\d+)|(\\((??(?:[^()]++|(?3))*)\\??)))", "pow(\\2, \\4)", v, perl=TRUE);
Run Code Online (Sandbox Code Playgroud)

并检查是否还有更多值要替换:

x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(?|()(\\d*\\.?\\d+)|(\\((??(?:[^()]++|(?3))*)\\??)))", v, perl=TRUE)
Run Code Online (Sandbox Code Playgroud)

  • 当没有明确的路径时,黑客攻击答案是有利的,但在这种情况下,R有一个确定的方法来实现这一点. (5认同)
  • @WiktorStribiżew你不需要删除它.这是一个有价值的答案,人们可​​以从中了解更多关于正则表达式的知识.投票将指向最受欢迎的答案,未来的用户可以考虑投票. (3认同)
  • 无需删除.从答案中可以学到很多东西.没有多少人知道像[^()] ++`这样的占有量词.*举手 (2认同)

Rol*_*and 12

这是一个以递归方式跟随解析树的解决方案并替换^:

#parse the expression
#alternatively you could create it with
#expression(((2-x+3)^2+(x-5+7)^10)^0.5)
e <- parse(text = "((2-x+3)^2+(x-5+7)^10)^0.5")

#a recursive function
fun <- function(e) {    
  #check if you are at the end of the tree's branch
  if (is.name(e) || is.atomic(e)) { 
    #replace ^
    if (e == quote(`^`)) return(quote(pow))
    return(e)
  }
  #follow the tree with recursion
  for (i in seq_along(e)) e[[i]] <- fun(e[[i]])
  return(e)    
}

#deparse to get a character string    
deparse(fun(e)[[1]])
#[1] "pow((pow((2 - x + 3), 2) + pow((x - 5 + 7), 10)), 0.5)"
Run Code Online (Sandbox Code Playgroud)

如果rapply使用表达式/调用,这将更容易.

编辑:

OP已询问有关表现的问题.性能不太可能是此任务的问题,但正则表达式解决方案并不快.

library(microbenchmark)
microbenchmark(regex = {
  v <- "((2-x+3)^2+(x-5+7)^10)^0.5"
  x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE)
  while(x) {
    v <- sub("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", "pow(\\2, \\3)", v, perl=TRUE);
    x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE)
  }
},
BrodieG = {
  deparse(f(parse(text = "((2-x+3)^2+(x-5+7)^10)^0.5")[[1]]))
},
Roland = {
  deparse(fun(parse(text = "((2-x+3)^2+(x-5+7)^10)^0.5"))[[1]])
})

#Unit: microseconds
#    expr     min      lq     mean  median      uq     max neval cld
#   regex 321.629 323.934 335.6261 335.329 337.634 384.623   100   c
# BrodieG 238.405 246.087 255.5927 252.105 257.227 355.943   100  b 
#  Roland 211.518 225.089 231.7061 228.802 235.204 385.904   100 a
Run Code Online (Sandbox Code Playgroud)

我没有包含@digEmAll提供的解决方案,因为很明显,具有那么多data.frame操作的解决方案会相对较慢.

EDIT2:

这是一个也处理的版本sqrt.

fun <- function(e) {    
  #check if you are at the end of the tree's branch
  if (is.name(e) || is.atomic(e)) { 
    #replace ^
    if (e == quote(`^`)) return(quote(pow))
    return(e)
  }
  if (e[[1]] == quote(sqrt)) {
    #replace sqrt
    e[[1]] <- quote(pow)
    #add the second argument
    e[[3]] <- quote(0.5)
  }
  #follow the tree with recursion
  for (i in seq_along(e)) e[[i]] <- fun(e[[i]])
  return(e)    
}

e <- parse(text = "sqrt((2-x+3)^2+(x-5+7)^10)")
deparse(fun(e)[[1]])
#[1] "pow(pow((2 - x + 3), 2) + pow((x - 5 + 7), 10), 0.5)"
Run Code Online (Sandbox Code Playgroud)


dig*_*All 7

这是一个利用R解析器(使用getParseData函数)的示例:

# helper function which turns getParseData result back to a text expression
recreateExpr <- function(DF,parent=0){
  elements <- DF[DF$parent == parent,]
  s <- ""
  for(i in 1:nrow(elements)){
    element <- elements[i,]
    if(element$terminal)
      s <- paste0(s,element$text)
    else
      s <- paste0(s,recreateExpr(DF,element$id))
  }
  return(s)  
}

expr <- "((2-x+3)^2+(x-5+7)^10)^0.5"

DF <- getParseData(parse(text=expr))[,c('id','parent','token','terminal','text')]

# let's find the parents of all '^' expressions
parentsOfPow <- unique(DF[DF$token == "'^'",'parent'])

# replace all the the 'x^y' expressions with 'pow(x,y)' 
for(p in parentsOfPow){
  idxs <- which(DF$parent == p)
  if(length(idxs) != 3){ stop('expression with '^' is not correct')  }

  idxtok1 <- idxs[1]
  idxtok2 <- idxs[2]
  idxtok3 <- idxs[3]

  # replace '^' token with 'pow'
  DF[idxtok2,c('token','text')] <- c('pow','pow')

  # move 'pow' token as first token in the expression
  tmp <- DF[idxtok1,]
  DF[idxtok1,] <- DF[idxtok2,]
  DF[idxtok2,] <- tmp

  # insert new terminals '(' ')' and ','
  DF <- rbind(
    DF[1:(idxtok2-1),],
    data.frame(id=max(DF$id)+1,parent=p,token=',',terminal=TRUE,text='(',
               stringsAsFactors=FALSE),
    DF[idxtok2,],
    data.frame(id=max(DF$id)+2,parent=p,token=',',terminal=TRUE,text=',',
               stringsAsFactors=FALSE),
    DF[(idxtok2+1):idxtok3,],
    data.frame(id=max(DF$id)+3,parent=p,token=')',terminal=TRUE,text=')',
               stringsAsFactors=FALSE),
    if(idxtok3<nrow(DF)) DF[(idxtok3+1):nrow(DF),] else NULL
  )
}

# print the new expression
recreateExpr(DF)

> [1] "pow((pow((2-x+3),2)+pow((x-5+7),10)),0.5)"
Run Code Online (Sandbox Code Playgroud)