これは正規表現を使用しません。実際、正規表現が問題を解決するのに十分強力であり、パーサーが必要かどうかはわかりません。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"
括弧のネストは、リストのネストになりました。さらにいくつかのヘルパー関数が必要です。1つ目は、特定の深さより下のすべてを折りたたんで、その深さより上のノードを破棄します。2つ目は、リストの要素の1つをまとめて機能させるための貼り付けの単なるラッパーです。
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関数ファミリーの1つを使用して関数をループします。
以前のすべてのコードを1つの関数にまとめます。
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"