Рекурсивные функции в R для применения регулярных выражений внутри вложенных круглых скобок

Я работаю с вложенными выражениями в R, и мне нужна помощь в написании рекурсивной функции для обработки этих выражений, начиная с самых внутренних круглых скобок.

Учитывая строку типа:

expr <- "(((a OR b) OR c) AND d) OR (e AND f)"

Я хочу выполнить следующие замены:

  1. Замените все вхождения «(i OR j)» на «min(1, i + j)».
  2. Замените все вхождения «(i AND j)» на «(i * j)»

Например:

  • «(a OR b)» должно стать «min(1, a + b)»
  • «(e AND f)» должно стать «(e * 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)

Кажется, что приведенный выше код неправильно обрабатывает вложенные круглые скобки, особенно при наличии нескольких уровней вложенности. Мне нужна функция, чтобы:

  1. Сначала определите самые сокровенные выражения.
  2. Правильно применяйте замены.
  3. Обработка нескольких уровней вложенных выражений.

Все решения, которые я рассмотрел, работают для Python или Perl, но не для R.

Есть ли другой подход, который я могу использовать, чтобы получить то, что мне нужно? то есть,

  1. Замените все вхождения «(i OR j)» на «min(1, i + j)».
  2. Замените все вхождения «(i AND j)» на «(i * j)»

Пример данных: «(((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))"

Основной крайний случай, который я сейчас не могу понять: string <- "(a AND b) OR (c AND d) + e" Необходимо оценить как "min(1, (a * b) + c * d) )) + e", но ни одно из решений/ответов ниже (пока на момент написания статьи) не анализирует выражение таким образом, потому что им нужно сначала начать с самых внутренних скобок.

statneutrino 24.06.2024 21:12

Вывод для второго примера неправильный. Обратите внимание, что + имеет приоритет над ||, поэтому (r1 && r2)|| r3 + r4 + (r5 && r6) структурно похож на (r1 && r2)|| (r3 + r4 + (r5 && r6)).

Onyambu 25.06.2024 03:29
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
111
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вам нужно, чтобы условие OR обязательно оценивалось как 1, если TRUE? Или, другими словами, было бы нормально, если бы (A OR B) оценивалось как 2 (что в логическом смысле все равно было бы TRUE) в тех случаях, когда и A, и B равны TRUE? Если да, то очень простым решением будет следующее:

gsub(" +AND +"," * ",gsub(" +OR +"," + ",expr))

Обратите внимание, что это также позаботится о любых дополнительных пробелах, которые вы случайно добавили вокруг выражений OR/AND.

Обновлено: Кроме того, как упомянул другой пользователь в комментарии под вашим вопросом, вам не хватает одной скобки в начале expr.

Большое спасибо за ваш ответ. Да, к сожалению, я требую, чтобы ответ был 1 или суммой. Хотя я только что понял, может быть, я мог бы просто разделить на 2?

statneutrino 24.06.2024 19:15

Это очень плохо, и я боюсь, что если вы разделите на 2, вы, к сожалению, столкнетесь с той же проблемой, когда только один из A или B равен TRUE, поскольку тогда ваш результат будет 0,5 (плюс приведенное выше выражение будет не таким простым) .

razorofockham 24.06.2024 19:23
Ответ принят как подходящий

Если мы преобразуем входные данные в действительный код 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 24.06.2024 21:07

@statneutrino Ну, раньше у вас было всего два оператора с четким порядком операций/приоритетом. Кажется, вы не хотите, чтобы оператор сложения имел более высокий приоритет, чем и/или. В этом случае я заменил остальные операторы на функции с более низким приоритетом.

MrFlick 24.06.2024 21:48

Удивительно! Большое спасибо! Я часами рвал на себе волосы, пытаясь понять это. Спасибо!

statneutrino 24.06.2024 22:17

Оп нарушает правила математики. Обратите внимание, что + имеет приоритет над ||, поэтому (r1 && r2)|| r3 + r4 + (r5 && r6) структурно похож на (r1 && r2)|| (r3 + r4 + (r5 && r6)).

Onyambu 25.06.2024 03:28

@Onyambu Но ОП не утверждает, что использует традиционные правила математики. Синтаксический анализатор может преобразовать произвольные токены в любой синтаксис, который вам нужен. Если оно четко определено и последовательно, все будет в порядке. Правила приоритета могут различаться в зависимости от языка.

MrFlick 25.06.2024 16:03

Я бы преобразовал текст в анализируемый код 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))"

Упс, я был слишком медленным - этот ответ структурно идентичен предыдущему, но использует нетривиально другой код, так что, может быть, мне стоит оставить его...?

Mikael Jagan 24.06.2024 19:38

Попытка использования замены строк:

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

Другие вопросы по теме