R: как различить внутренние и внутренние скобки с помощью регулярных выражений
Что мне нужно от строки ((VBD)(((JJ))(CC)((RB)(JJ)))((IN)((DT)(JJ)(NNP)(NNPS))))
это:
"JJ", "RBJJ", "DTJJNNPNNPS", "JJCCRBJJ", "INDTJJNNPNNPS" "VBDJJCCRBJJINDTJJNNPNNPS"
то есть, чтобы найти текст в самых внутренних скобках, удалите непосредственно окружающие скобки, чтобы текст можно было объединить и извлечь. Но это состоит из разных уровней. Раскрытие скобок не может быть сделано сразу, потому что нет скобок выходит из равновесия:
str1<-c()
str2<-c()
library(gsubfn)
strr<-c("((VBD)(((JJ))(CC)((RB)(JJ)))((IN)((DT)(JJ)(NNP)(NNPS))))")
repeat {
str1<-unlist(strapply(strr, "((\\(([A-Z])+\\))+)"))
str2<-append(str1, str2)
strr<-gsub("(\\(\\w+\\))", "~\\1~", strr)
strr<-gsub("~\\(|\\)~", "", strr)
if (strr == "") {break}
}
strr
[1] "(VBD(JJCCRBJJINDTJJNNPNNPS"
В скобках оставлено блокирование объединения текста, что позволяет ему избежать регулярного выражения. Я думаю, что решение этой проблемы состоит в том, чтобы провести различие между внутренними скобками (JJ, RB, JJ, DT, JJ, NNP, NNPS, (2, 4, 5, 7, 8, 9, 10 в новой строке)) и внутренними скобки. Таким образом, когда все внутренние скобки будут открыты шаг за шагом, а текст объединен и извлечен, мы получим всю строку. Есть ли регулярное выражение для этого? Или есть другой способ? Пожалуйста помоги.
3 ответа
Это не использует регулярное выражение. На самом деле, я не уверен, что регулярные выражения достаточно мощны, чтобы решить проблему, и что анализатор необходим. Вместо того, чтобы создавать / определять парсер в R, я использую существующие R
парсер кода Для этого используются некоторые потенциально опасные уловки.
Основная идея состоит в том, чтобы превратить строку в анализируемый код, который генерирует древовидную структуру, используя списки. Затем эта структура эффективно сокращается в обратном направлении (сохраняя только листовой узел внутри), и создаются различные строки на каждом уровне.
Некоторые вспомогательные пакеты
library("plotrix")
library("plyr")
Исходная строка, которую вы дали
strr<-c("((VBD)(((JJ))(CC)((RB)(JJ)))((IN)((DT)(JJ)(NNP)(NNPS))))")
Превратите эту строку в анализируемый код, заключив в кавычки то, что находится внутри скобок, а затем сделав каждый набор скобок вызовом list
, Запятые должны быть вставлены между элементами списка, но самые внутренние части всегда представляют собой списки длиной 1, так что это не проблема. Затем разберите код.
tmp <- gsub("\\(([^\\(\\)]*)\\)", '("\\1")', strr)
tmp <- gsub("\\(", "list(", tmp)
tmp <- gsub("\\)list", "),list", tmp)
tmp <- eval(parse(text=tmp))
С этой точки зрения, tmp
похоже
> str(tmp)
List of 3
$ :List of 1
..$ : chr "VBD"
$ :List of 3
..$ :List of 1
.. ..$ :List of 1
.. .. ..$ : chr "JJ"
..$ :List of 1
.. ..$ : chr "CC"
..$ :List of 2
.. ..$ :List of 1
.. .. ..$ : chr "RB"
.. ..$ :List of 1
.. .. ..$ : chr "JJ"
$ :List of 2
..$ :List of 1
.. ..$ : chr "IN"
..$ :List of 4
.. ..$ :List of 1
.. .. ..$ : chr "DT"
.. ..$ :List of 1
.. .. ..$ : chr "JJ"
.. ..$ :List of 1
.. .. ..$ : chr "NNP"
.. ..$ :List of 1
.. .. ..$ : chr "NNPS"
Вложенность скобок теперь является вложением списков. Требуется еще несколько вспомогательных функций. Первое сворачивает все ниже определенной глубины и отбрасывает любой узел выше этой глубины. Второй - просто обертка для вставки для совместной работы с элементами списка.
atdepth <- function(l, d) {
if (d > 0 & !is.list(l)) {
return(NULL)
}
if (d == 0) {
return(unlist(l))
}
if (is.list(l)) {
llply(l, atdepth, d-1)
}
}
pastelist <- function(l) {paste(unlist(l), collapse="", sep="")}
Создайте список, где каждый элемент древовидной структуры свернут на определенную глубину.
down <- llply(1:listDepth(tmp), atdepth, l=tmp)
Итерируя в обратном направлении по этому списку, вставьте наборы листьев вместе. Работайте задом наперед, "вверх" (свернутые) деревья. В результате получается несколько пустых строк (там, где был лист выше), поэтому они обрезаются.
out <- if (length(down) > 2) {
c(unlist(llply(length(down):3, function(i) {
unlist(do.call(llply, c(list(down[[i]]), replicate(i-3, llply), pastelist)))
})), unlist(pastelist(down[[2]])))
} else {
unlist(pastelist(down[[2]]))
}
out <- out[out != ""]
В результате я думаю, что вы просили:
> out
[1] "JJ" "RBJJ"
[3] "DTJJNNPNNPS" "JJCCRBJJ"
[5] "INDTJJNNPNNPS" "VBDJJCCRBJJINDTJJNNPNNPS"
> dput(out)
c("JJ", "RBJJ", "DTJJNNPNNPS", "JJCCRBJJ", "INDTJJNNPNNPS", "VBDJJCCRBJJINDTJJNNPNNPS"
)
РЕДАКТИРОВАТЬ:
В ответ на комментарий с последующим вопросом: как адаптировать его для обработки набора этих строк.
Общий подход к решению задач "сделай это несколько раз для разных входов" заключается в создании функции, которая принимает один элемент в качестве входных данных и возвращает соответствующий единственный выход. Затем зациклите функцию с помощью одного из семейств функций apply.
Объединение всего кода из ранее в одну функцию:
parsestrr <- function(strr) {
atdepth <- function(l, d) {
if (d > 0 & !is.list(l)) {
return(NULL)
}
if (d == 0) {
return(unlist(l))
}
if (is.list(l)) {
llply(l, atdepth, d-1)
}
}
pastelist <- function(l) {paste(unlist(l), collapse="", sep="")}
tmp <- gsub("\\(([^\\(\\)]*)\\)", '("\\1")', strr)
tmp <- gsub("\\(", "list(", tmp)
tmp <- gsub("\\)list", "),list", tmp)
tmp <- eval(parse(text=tmp))
down <- llply(1:listDepth(tmp), atdepth, l=tmp)
out <- if (length(down) > 2) {
c(unlist(llply(length(down):3, function(i) {
unlist(do.call(llply, c(list(down[[i]]), replicate(i-3, llply), pastelist)))
})), unlist(pastelist(down[[2]])))
} else {
unlist(pastelist(down[[2]]))
}
out[out != ""]
}
Теперь, учитывая вектор строк для обработки, скажем:
strrs<-c("((VBD)(((JJ))(CC)((RB)(JJ)))((IN)((DT)(JJ)(NNP)(NNPS))))",
"((VBD)(((JJ))(CC)((RB)(XX)(JJ)))((IN)(BB)((DT)(JJ)(NNP)(NNPS))))",
"((VBD)(((JJ)(QQ))(CC)((RB)(JJ)))((IN)((TQR)(JJ)(NNPS))))")
Вы можете обработать их все
llply(strr, parsestrr)
который возвращается
[[1]]
[1] "JJ" "RBJJ"
[3] "DTJJNNPNNPS" "JJCCRBJJ"
[5] "INDTJJNNPNNPS" "VBDJJCCRBJJINDTJJNNPNNPS"
[[2]]
[1] "JJ" "RBXXJJ"
[3] "DTJJNNPNNPS" "JJCCRBXXJJ"
[5] "INBBDTJJNNPNNPS" "VBDJJCCRBXXJJINBBDTJJNNPNNPS"
[[3]]
[1] "JJQQ" "RBJJ"
[3] "TQRJJNNPS" "JJQQCCRBJJ"
[5] "INTQRJJNNPS" "VBDJJQQCCRBJJINTQRJJNNPS"
Я не уверен, хотите ли вы просто построить древовидную структуру сбалансированного текста или нет.
Или, почему вы хотите удалить круглые скобки на самом внутреннем уровне.
Используя ваш пример, если это должно быть сделано поэтапно, самый внутренний уровень должен быть первоначально определен. Затем скобки удаляются на последующих уровнях в рекурсивных проходах.
Это, конечно, требует способ сделать сбалансированный текст. Некоторые движки регулярных выражений могут сделать это.
Если используемый вами движок не поддерживает это, это нужно будет сделать вручную с помощью обработки текста.
У меня есть программа анализа регулярных выражений. Я накачал вашу исходную строку в нее, и она визуально отформатировала ее на уровне группы. Каждый проход я просто лишал внутреннего родителя, который имитирует рекурсию.
Может быть, это поможет вам визуализировать, что нужно сделать.
## Pass 0
## ---------
(
( VBD )
(
(
( JJ )
)
( CC )
(
( RB )
( JJ )
)
)
(
( IN )
(
( DT )
( JJ )
( NNP )
( NNPS )
)
)
)
## Pass 1
## ---------
(
( VBD )
(
( JJ )
( CC )
( RB JJ )
)
(
( IN )
( DT JJ NNP NNPS )
)
)
## Pass 2
## ---------
(
( VBD )
( JJ CC RB JJ )
( IN DT JJ NNP NNPS )
)
## Pass 3
## ---------
( VBD JJ CC RB JJ IN DT JJ NNP NNPS )
## Pass 4
## ---------
VBD JJ CC RB JJ IN DT JJ NNP NNPS
Вам не нужно думать о совпадении скобок здесь... Похоже, вы просто хотите рекурсивно сопоставить шаблон [()]([^()]*)[()]
,
То есть "сопоставить то, что не содержит (
)
и разграничены (
или же )
"