Замените символ ^ (мощность) синтаксисом 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)"