Замените символ ^ (мощность) синтаксисом C pow в математическом выражении

У меня есть математическое выражение, например:

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

Мне нужно заменить символ ^ на pow функцию языка C. Я думаю, что регулярное выражение - это то, что мне нужно, но я не знаю регулярное выражение, как профессионал. Итак, я закончил с этим регулярным выражением:

(\([^()]*)*(\s*\([^()]*\)\s*)+([^()]*\))*

Я не знаю, как это улучшить. Можете ли вы посоветовать мне что-то решить эту проблему?

Ожидаемый результат:

pow(pow(2-x+3,2)+pow(x-5+7,10),0.5)

Ответы

Ответ 1

Вот решение, которое следует за деревом разбора рекурсивно и заменяет ^:

#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 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)"

Это было бы намного проще, если rapply работал с выражениями/вызовами.

Edit:

ОП задал вопрос о производительности. Очень маловероятно, что производительность является проблемой для этой задачи, но решение регулярного выражения выполняется не быстрее.

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

Я не включил решение, предоставленное @digEmAll, потому что кажется очевидным, что решение с таким количеством операций data.frame будет относительно медленным.

Edit2:

Вот версия, которая также обрабатывает sqrt.

fun <- function(e) {    
  #check if you are at the end of the tree 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)"

Ответ 2

Одна из самых фантастических вещей в R заключается в том, что вы можете легко манипулировать R-выражениями с R. Здесь мы рекурсивно пересекаем ваше выражение и заменяем все экземпляры ^ на 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)

Это должно быть более надежным, чем регулярное выражение, поскольку вы полагаетесь на естественную интерпретацию языка R, а не на текстовые шаблоны, которые могут быть или не быть исчерпывающими.


Детали. Вызовы в R сохраняются в списках типа структур с помощью функции /operator во главе списка и аргументами в следующих элементах. Например, рассмотрим:

exp <- quote(x ^ 2)
exp
## x^2
is.call(exp)
## [1] TRUE

Мы можем проверить базовую структуру вызова с помощью as.list:

str(as.list(exp))
## List of 3
##  $ : symbol ^
##  $ : symbol x
##  $ : num 2

Как вы можете видеть, первым элементом является оператор function/, а последующие элементы являются аргументами функции.

Итак, в нашей рекурсивной функции мы:

  • Проверить, является ли объект вызовом
    • Если да: проверьте, является ли это вызовом функции ^ function/operator, посмотрев на первый элемент в вызове с помощью identical(x[[1L]], as.name("^")
      • Если да: замените первый элемент на as.name("pow")
      • Тогда, независимо от того, был ли это вызов ^ или что-то еще:
        • если вызов имеет дополнительные элементы, цикл через них и применить эту функцию (т.е. recurse) к каждому элементу, заменив результат обратно на исходный вызов (x[2L:length(x)] <- lapply(x[2L:length(x)], f))
    • Если нет: просто верните объект без изменений

Обратите внимание, что вызовы часто содержат имена функций в качестве первого элемента. Вы можете создать эти имена с помощью as.name. Имена также упоминаются как "символы" в R (следовательно, вывод str).

Ответ 3

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ. Ответ был написан с оригинальным регулярным выражением OP, когда вопрос звучал как "процесс ^, которому предшествуют сбалансированные (вложенные) круглые скобки". Пожалуйста, не использовать это решение для синтаксического разбора математического выражения только для образовательных целей и только тогда, когда вам действительно нужно обработать некоторый текст в контексте сбалансированных круглых скобок.

Так как регулярное выражение PCRE может соответствовать вложенным круглым скобкам, в R можно достичь простого регулярного выражения в цикле while, проверяя наличие ^ в модифицированной строке с помощью x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(\\d*\\.?\\d+)", v, perl=TRUE). Если нет ^, заменить его нечем.

Шаблон регулярного выражения

(\(((?:[^()]++|(?1))*)\))\^(\d*\.?\d+)

Смотрите демонстрацию regex

Подробнее

  • (\(((?:[^()]++|(?1))*)\)) - Группа 1: подстрока (...) со сбалансированными скобками, захватывающая то, что находится внутри внешних круглых скобок, в группу 2 (с подшаблоном ((?:[^()]++|(?1))*)) (объяснение можно найти на Как я могу сопоставить вложенные скобки с помощью regex?), короче говоря, \ соответствует внешнему (, тогда (?:[^()]++|(?1))* соответствует нулю или более последовательностям символов 1+, отличных от ( и ) или весь подпаптер 1-й группы ((?1)является подпрограммой подпрограммы), а затем a ))
  • \^ - a ^ карет
  • (\d*\.?\d+) - Группа 3: число int/float (.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)"

И для поддержки ^(x-3) pow s вы можете использовать

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

и проверить, есть ли какие-либо значения для замены:

x <- grepl("(\\(((?:[^()]++|(?1))*)\\))\\^(?|()(\\d*\\.?\\d+)|(\\((‌​(?:[^()]++|(?3))*)\\‌​)))", v, perl=TRUE)

Ответ 4

Вот пример использования 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 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)"