Я работаю с вложенными выражениями в R, и мне нужна помощь в написании рекурсивной функции для обработки этих выражений, начиная с самых внутренних круглых скобок.
Учитывая строку типа:
expr <- "(((a OR b) OR c) AND d) OR (e AND f)"
Я хочу выполнить следующие замены:
Например:
Строка "((a OR b) OR c) AND d) OR (e AND f)" должна иметь следующий результат:
"min(1, (min(1, min(1, a + b) + c) * d) + (e * f))"
Проблема в том, что выражения могут быть вложенными, и мне нужно сначала обработать самые внутренние круглые скобки, прежде чем двигаться наружу. Это потому, что я пишу функции для ввода данных пользователем для целевых функций, и у меня есть код R для динамической записи кода Rcpp...
Я рассматривал возможность использования регулярных выражений для поиска и замены шаблонов, но вот с рекурсивной обработкой вложенных структур я застрял. Я попробовал подход к рекурсивной функции следующим образом:
process_expr <- function(string) {
# Match the innermost parentheses
pattern <- "\\(([^()]*)\\)"
brackets <- gregexpr(pattern, string, perl = TRUE)
matches <- regmatches(string, brackets)
# If there are no more matches, return the string
if (length(matches[[1]]) == 0) {
return(string)
}
# Process each match
for (match in matches[[1]]) {
# Apply the replacements
modified <- gsub("\\(([^()]+) OR ([^()]+)\\)", "min(1, \\1 + \\2)", match)
modified <- gsub("\\(([^()]+) AND ([^()]+)\\)", "(\\1 * \\2)", modified)
# Replace the match in the original string
string <- sub(pattern, modified, string, fixed = TRUE)
}
# Recurse to process the next level of nested parentheses
return(process_expr(string))
}
# Example usage
expr <- "((a OR b) OR c) AND d) OR (e AND f)"
result <- process_expr(expr)
print(result)
Кажется, что приведенный выше код неправильно обрабатывает вложенные круглые скобки, особенно при наличии нескольких уровней вложенности. Мне нужна функция, чтобы:
Все решения, которые я рассмотрел, работают для Python или Perl, но не для R.
Есть ли другой подход, который я могу использовать, чтобы получить то, что мне нужно? то есть,
Пример данных: «(((a OR b) OR c) AND d) OR (e AND f)»
Необходимо оценить:
min(1, (min(1, min(1, a + b) + c) * d) + (e * f))
Обновлено:
Вот еще несколько примеров ввода-вывода:
input_2 <- "(r1 && r2) || r3 + r4 + (r5 && r6)"
вывод_2: "min(1, (r1 * r2) + r3) + r4) + (r6 * r7)"
Вот еще несколько примеров ввода-вывода:
input_3 <- "(r1 && r2) || (r3 && r4) + r5 || (r6 + r7)"
вывод_3: "min(1, (r1 * r2) + (r3 * r4)) + min(1, r5 + (r6 + r7))"
Вывод для второго примера неправильный. Обратите внимание, что +
имеет приоритет над ||
, поэтому (r1 && r2)|| r3 + r4 + (r5 && r6)
структурно похож на (r1 && r2)|| (r3 + r4 + (r5 && r6))
.
Вам нужно, чтобы условие OR
обязательно оценивалось как 1, если TRUE
? Или, другими словами, было бы нормально, если бы (A OR B)
оценивалось как 2 (что в логическом смысле все равно было бы TRUE
) в тех случаях, когда и A
, и B
равны TRUE
? Если да, то очень простым решением будет следующее:
gsub(" +AND +"," * ",gsub(" +OR +"," + ",expr))
Обратите внимание, что это также позаботится о любых дополнительных пробелах, которые вы случайно добавили вокруг выражений OR
/AND
.
Обновлено: Кроме того, как упомянул другой пользователь в комментарии под вашим вопросом, вам не хватает одной скобки в начале expr
.
Большое спасибо за ваш ответ. Да, к сожалению, я требую, чтобы ответ был 1 или суммой. Хотя я только что понял, может быть, я мог бы просто разделить на 2?
Это очень плохо, и я боюсь, что если вы разделите на 2, вы, к сожалению, столкнетесь с той же проблемой, когда только один из A
или B
равен TRUE
, поскольку тогда ваш результат будет 0,5 (плюс приведенное выше выражение будет не таким простым) .
Если мы преобразуем входные данные в действительный код R, мы можем использовать анализ и замену. Например, эта вспомогательная функция делает свое дело
transform <- function(expr) {
tx <- function(e) {
if (is.call(e)) {
parts <- as.list(e)
if (parts[[1]] == quote(`%OR%`)) {
parts[[1]] <- quote(`min`)
parts[[3]] <- bquote(.(tx(parts[[2]])) + .(tx(parts[[3]])))
parts[[2]] <- 1
as.call(parts)
} else if (parts[[1]]==quote(`%AND%`)) {
parts[[1]] <- quote(`*`)
parts[[2]] <- tx(parts[[2]])
parts[[3]] <- tx(parts[[3]])
as.call(parts)
} else {
parts[-1] <- lapply(parts[-1], tx)
as.call(parts)
}
} else {
e
}
}
fix <- gsub("\\bOR\\b", "%OR%", gsub("\\bAND\\b", "%AND%", expr))
fix <- gsub("\\|\\|", "%OR%", gsub("\\&\\&", "%AND%", fix))
deparse1(tx(str2lang(fix)))
}
С помощью которого мы можем протестировать
expr <- "(((a OR b) OR c) AND d) OR (e AND f)"
transform(expr)
# [1] "min(1, ((min(1, (min(1, a + b)) + c)) * d) + (e * f))"
input_2 <- "(r1 && r2) || r3 + r4 + (r5 && r6)"
transform(input_2)
# [1] "min(1, (r1 * r2) + r3) + r4 + (r5 * r6)"
input_3 <- "(r1 && r2) || (r3 && r4) + r5 || (r6 + r7)"
transform(input_3)
# [1] "min(1, (r1 * r2) + (r3 * r4)) + min(1, r5 + (r6 + r7))"
Мы превращаем OR и AND в %OR%
и %AND%
, которые можно проанализировать с помощью R, а затем пройти по абстрактному синтаксическому дереву и выполнить преобразование. Анализ выполняет тяжелую работу по построению дерева.
Удивительно, это изящное решение, спасибо. Это отлично работает для первого примера ввода. Я не уверен, почему это не работает для input_2 и input_3?
@statneutrino Ну, раньше у вас было всего два оператора с четким порядком операций/приоритетом. Кажется, вы не хотите, чтобы оператор сложения имел более высокий приоритет, чем и/или. В этом случае я заменил остальные операторы на функции с более низким приоритетом.
Удивительно! Большое спасибо! Я часами рвал на себе волосы, пытаясь понять это. Спасибо!
Оп нарушает правила математики. Обратите внимание, что +
имеет приоритет над ||
, поэтому (r1 && r2)|| r3 + r4 + (r5 && r6)
структурно похож на (r1 && r2)|| (r3 + r4 + (r5 && r6))
.
@Onyambu Но ОП не утверждает, что использует традиционные правила математики. Синтаксический анализатор может преобразовать произвольные токены в любой синтаксис, который вам нужен. Если оно четко определено и последовательно, все будет в порядке. Правила приоритета могут различаться в зависимости от языка.
Я бы преобразовал текст в анализируемый код R, проанализировал, рекурсивно просмотрел полученный вызов, а затем разобрал:
(s0 <- "(((a OR b) OR c) AND d) OR (e AND f)")
## [1] "(((a OR b) OR c) AND d) OR (e AND f)"
(s1 <- gsub(" OR ", " | ", gsub(" AND ", " & ", s0)))
## [1] "(((a | b) | c) & d) | (e & f)"
recurse <- function(x) {
if (!is.call(x))
x
else if ((n <- length(x)) == 3L) {
if (identical(x[[1L]], quote(`|`)))
call("min", 1, call("+", Recall(x[[2L]]), Recall(x[[3L]])))
else if (identical(x[[1L]], quote(`&`)))
call("*", Recall(x[[2L]]), Recall(x[[3L]]))
else x
}
else {
if (n >= 2L) for (i in 2L:n) x[[i]] <- Recall(x[[i]])
x
}
}
(s2 <- deparse(recurse(str2lang(s1))))
## [1] "min(1, ((min(1, (min(1, a + b)) + c)) * d) + (e * f))"
Упс, я был слишком медленным - этот ответ структурно идентичен предыдущему, но использует нетривиально другой код, так что, может быть, мне стоит оставить его...?
Попытка использования замены строк:
string <- "(((a OR b) OR c) AND d) OR (e AND f)"
# function to substitute a single expression
process_expression <- function(expression) {
# check if expression is something OR something, or something AND something, with optional parentheses
if (grepl("^[^()]+\\s+(?:OR|AND)\\s+[^()]+$", expression) || grepl("^\\([^()]+\\s+(?:OR)|(?:AND)\\s+[^()]+\\)$", expression)) {
# convert to required output, using braces {} to avoid clashes with the () in string
result <- gsub("\\(?([^()]+)\\s+OR\\s+([^()]+)\\)?", "min{1, \\1 + \\2}", expression)
result <- gsub("\\(?([^()]+)\\s+AND\\s+([^()]+)\\)?", "{\\1 * \\2}", result)
} else {
stop("Invalid expression")
}
return(result)
}
# function to find and replace all expressions in a string
process_string <- function(string) {
# find the (innermost) expressions
match_data <- gregexpr("\\(?[^()]+\\s+(?:OR|AND)\\s+[^()]+\\)?", string)
# get the match strings
matches <- regmatches(string, match_data)[[1]]
# substitute the matches for their processed equivalents
result <- Reduce(\(s, m) gsub(m, process_expression(m), s, fixed = TRUE), matches, init=string)
# recurse if any AND or OR remain
if (grepl("(AND|OR)", result)) {
result <- process_string(result)
}
# change braces back to parentheses
result <- gsub("{", "(", gsub("}", ")", result, fixed = TRUE), fixed = TRUE)
return(result)
}
process_string(string)
#> [1] "min(1, (min(1, min(1, a + b) + c) * d) + (e * f))"
Created on 2024-06-24 with reprex v2.1.0
string_parse <- function(x){
if (is.call(x)){
if (x[[1]] == '|') x <- call('min', 1, call('+', x[[2]], x[[3]]))
else if (x[[1]] == '&') x[[1]] <- as.name('*')
x[-1] <- lapply(x[-1], string_parse)
}
x
}
string <- "(((a OR b) OR c) AND d) OR (e AND f)"
string_parse(str2lang(gsub("AND", "&", gsub("OR", "|", string))))
min(1, ((min(1, (min(1, a + b)) + c)) * d) + (e * f))
Основной крайний случай, который я сейчас не могу понять: string <- "(a AND b) OR (c AND d) + e" Необходимо оценить как "min(1, (a * b) + c * d) )) + e", но ни одно из решений/ответов ниже (пока на момент написания статьи) не анализирует выражение таким образом, потому что им нужно сначала начать с самых внутренних скобок.